«Шаг за шагом» при отладке многопоточных программ в Visual Studio - PullRequest
26 голосов
/ 03 декабря 2008

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

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

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

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

Ответы [ 6 ]

39 голосов
/ 18 декабря 2008

Я думаю, что есть только один ответ на ваш вопрос, который вы отклонили как «слишком много работы». Тем не менее, я считаю, что это потому, что вы делаете это неправильно. Позвольте мне представить шаги для добавления условной точки останова к идентификатору потока, которые чрезвычайно просты, но не очевидны, пока вы их не знаете.

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

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

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

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

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

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

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

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

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

Вот они и удачи на охоте на жуков !!

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

Чтобы добавить комбинации клавиш для запуска этих макросов в Visual Studio:
1. Откройте Параметры (в меню выберите: Инструменты-> Параметры )
2. Разверните до Среда-> Клавиатура
3. В Показать команды, содержащие: , введите Макросы. , чтобы увидеть все ваши макросы.
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 и ранее.

21 голосов
/ 03 декабря 2008

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

6 голосов
/ 23 ноября 2010

Простой способ отладки одного отдельного потока - заморозить все остальные потоки из окна потоков.

2 голосов
/ 24 сентября 2014

Очевидно, что 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 имеет смысл, но для меня все это выглядит как недокументированная особенность ...

2 голосов
/ 08 февраля 2013

[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

1 голос
/ 28 октября 2017

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

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

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

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

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

...