"Шаг за шагом" при отладке многопоточных программ в Visual Studio

Одна вещь, которая раздражает меня при отладке программ в Visual Studio (в моем случае в 2005 году), заключается в том, что когда я использую "step over" (нажав F10) для выполнения следующей строки кода, я часто заканчиваю эта конкретная строка кода в совершенно другом потоке, чем тот, на который я смотрел. Это означает, что весь контекст того, что я делал, был потерян.

Как мне обойти это?

Если это возможно сделать в более поздних версиях Visual Studio, я хотел бы также услышать об этом.

Установка точки останова на следующей строке кода, которая имеет условный разрыв только для этого потока, не является ответом, который я ищу, поскольку для меня слишком много работы, чтобы быть полезной для меня:)

Ответ 1

Я думаю, что есть только один ответ на ваш вопрос, который вы уклонились от того, чтобы "слишком много работать". Однако я считаю, что это потому, что вы идете по этому пути неправильно. Позвольте мне представить шаги для добавления условной точки останова на Thread ID, которые чрезвычайно просты, но не очевидны, пока вы их не узнаете.

  • Остановите отладчик в том месте, где вы находитесь в правильном потоке, который хотите продолжить отладку (который, как я предполагал, обычно является первым потоком, который попадает туда).

  • Введите $TID в окно просмотра.

  • Добавить точку останова с условием $TID == < значения $TID из окна просмотра >,
    Пример: $TID == 0x000016a0

  • Продолжить выполнение.

$TID - волшебная переменная для компиляторов Microsoft (по крайней мере, Visual Studio 2003), которая имеет значение текущего идентификатора потока. Это делает его намного проще, чем смотреть (FS + 0x18) [0x24]. = D

При этом вы можете получить то же поведение, что и контрольные точки One-Shot отладчика с некоторыми простыми макросами. Когда вы перешагиваетесь, отладчик, за кулисами, устанавливает точку останова, переходит к этой точке останова, а затем удаляет ее. Ключ к последовательному пользовательскому интерфейсу удаляет эти точки останова, если удалена точка останова ЛЮБОЙ.

Следующие два макроса предоставляют Step Over и Run To Cursor для текущего потока. Это выполняется так же, как и отладчик, причем точки останова удаляются после выполнения, независимо от того, какая точка останова была удалена.

Вы хотите назначить комбинацию клавиш для их запуска.

ПРИМЕЧАНИЕ: одно предупреждение - макрос "Step Over" работает только корректно, если курсор находится в строке, которую вы хотите перешагнуть. Это связано с тем, что он определяет текущее местоположение по местоположению курсора и просто добавляет его к номеру строки. Возможно, вы сможете заменить расчет местоположения информацией о текущей точке выполнения, хотя мне не удалось найти эту информацию в среде Macro IDE.

Вот они и охота на удачу удачи!!

Чтобы использовать эти макросы в Visual Studio:
1. Откройте Macro IDE (в меню выберите: Инструменты- > Макросы- > Макро-IDE...)
2. Добавьте новый файл кода (из меню: выберите: Project- > Добавить новый элемент..., выберите "Файл кода" и нажмите "Добавить" )
3. Вставьте в этот код.
4. Сохраните файл.

Чтобы добавить комбинации клавиш для запуска этих макросов в Visual Studio:
1. Откройте" Параметры "(в меню выберите" Инструменты "-" Параметры ")
2. Разверните в Environment- > Keyboard
3. В командах Show, содержащих:, введите Макросы., чтобы просмотреть все ваши макросы.
4. Выберите макрос, затем нажмите "Нажмите". Нажмите клавиши быстрого доступа:
5. Введите комбо, которое вы хотите использовать (backspace удаляет типизированные комбо)
6. Нажмите "Назначить", чтобы настроить ярлык для запуска выбранного макроса.

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics

Public Module DebugHelperFunctions

    Sub RunToCursorInMyThread()
        Dim textSelection As EnvDTE.TextSelection
        Dim myThread As EnvDTE.Thread
        Dim bp As EnvDTE.Breakpoint
        Dim bps As EnvDTE.Breakpoints

        ' For Breakpoints.Add()
        Dim FileName As String
        Dim LineNumber As Integer
        Dim ThreadID As String

        ' Get local references for ease of use 
        myThread = DTE.Debugger.CurrentThread
        textSelection = DTE.ActiveDocument.Selection

        LineNumber = textSelection.ActivePoint.Line
        FileName = textSelection.DTE.ActiveDocument.FullName
        ThreadID = myThread.ID

        ' Add a "One-Shot" Breakpoint in current file on current line for current thread
        bps = DTE.Debugger.Breakpoints.Add("", FileName, LineNumber, 1, "$TID == " & ThreadID)

        ' Run to the next stop
        DTE.Debugger.Go(True)

        ' Remove our "One-Shot" Breakpoint
        For Each bp In bps
            bp.Delete()
        Next
    End Sub

    Sub StepOverInMyThread()
        Dim textSelection As EnvDTE.TextSelection
        Dim myThread As EnvDTE.Thread
        Dim bp As EnvDTE.Breakpoint
        Dim bps As EnvDTE.Breakpoints

        ' For Breakpoints.Add()
        Dim FileName As String
        Dim LineNumber As Integer
        Dim ThreadID As String

        ' Get local references for ease of use 
        myThread = DTE.Debugger.CurrentThread
        textSelection = DTE.ActiveDocument.Selection

        LineNumber = textSelection.ActivePoint.Line
        FileName = textSelection.DTE.ActiveDocument.FullName
        ThreadID = myThread.ID
        LineNumber = LineNumber + 1

        ' Add a "One-Shot" Breakpoint in current file on current line for current thread
        bps = DTE.Debugger.Breakpoints.Add("", FileName, LineNumber, 1, "$TID == " & ThreadID)

        ' Run to the next stop
        DTE.Debugger.Go(True)

        ' Remove our "One-Shot" Breakpoint
        For Each bp In bps
            bp.Delete()
        Next
    End Sub


End Module

Отказ от ответственности. Я написал эти макросы в Visual Studio 2005. Возможно, вы можете использовать их в Visual Studio 2008. Они могут потребовать изменения для Visual Studio 2003 и раньше.

Ответ 2

Вы можете заморозить другой поток или переключиться на другой поток, используя окно отладки Threads (Ctrl + Alt + H).

Ответ 3

Простым способом отладки одного конкретного потока является замораживание всех остальных потоков из окна Threads.

Ответ 4

[Ctrl + D, T] или [Ctrl + Alt + H] - открывает окно потока (используется для контроля, замораживания и имен потоков)

В окне потока вы можете выбрать, хотите ли вы показывать местоположение других потоков в visual studio. Это хорошее напоминание мне, что текущий поток, который я отлаживаю, не единственный в игре. Наведение курсора на маркер потока дает вам имя и идентификатор потоков.

Дополнительные советы, найденные по адресу: http://devpinoy.org/blogs/jakelite/archive/2009/01/10/5-tips-on-debugging-multi-threaded-code-in-visual-studio-net.aspx

Ответ 5

По-видимому, Visual Studio 2010 переключается только на другие потоки, если вы нажмете F10, когда отладчику пришлось сломать этот поток раньше или если установлена ​​точка останова, которая будет удалена в этом потоке.

Я использовал следующий код для проверки поведения:

class Program
{
    static void Main(string[] args)
    {
        var t = new Thread(new ThreadStart(Work));
        t.Start();

        for (int i = 0; i < 20; i++)
        {
            Thread.Sleep(1000);
            Console.WriteLine("............");
        }

        t.Join();
    }

    static void Work()
    {
        for (int i = 0; i < 20; i++)
        {
            Thread.Sleep(1000);
            Console.WriteLine("ZZzzzzzzzzzzzzzz");
        }
    }
}

Если вы просто переходите к программе или добавляете точку останова в методе Main(), нажатие F10 выполняется только через код из основного потока.

Если вы добавили точку останова в метод Work(), отладчик выполнит оба потока.

Такое поведение Visual Studio имеет смысл, но все это похоже на недокументированную функцию для меня...

Ответ 6

Недавно у меня была та же проблема отладки только определенного потока. Хотя я не буду упускать из виду вышеупомянутый ответ как очень всеобъемлющий и ценный (и хотел бы, чтобы я нашел его 2 дня назад) - я применил следующее, чтобы помочь "повседневному" устранению неполадок.

Эта идея является всего лишь обходным решением при запуске нескольких экземпляров одного и того же класса в нескольких потоках, которые всегда применяются нашим приложением.

Мы инициализируем все экземпляры классов с уникальным идентификатором или именем, поэтому мы знаем, как и почему создавался экземпляр. Затем, когда нам нужно отлаживать конкретный экземпляр (то есть поток), а не замораживать другие потоки, добавим следующее:

if (instance.ID == myID)
{
    // Assert BreakPoint
}

В нашем случае мы устанавливаем фиксированные идентификаторы, поэтому мы знаем идентификатор, который мы хотим устранить, если у нас есть проблема.