Поделиться через


Пошаговое руководство. Отладка параллельного приложения в Visual Studio (C#, Visual Basic, C++)

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

В этом пошаговом руководстве описаны следующие задачи:

  • Как просмотреть стеки вызовов всех потоков в одном представлении.

  • Как просмотреть список экземпляров System.Threading.Tasks.Task, созданных в вашем приложении.

  • Как просмотреть реальные стеки вызовов задач вместо потоков.

  • Переход к коду из окон параллельных задач и параллельных стеков .

  • Как окна справляются с масштабами с помощью группирования, увеличения и других связанных функций.

Предпосылки

В этом пошаговом руководстве предполагается, что только мой код включен (он включен по умолчанию в более поздних версиях Visual Studio). В меню "Сервис" выберите пункт "Параметры", разверните узел отладки, выберите "Общие", а затем выберите "Включить только мой код" (только для управляемого кода). Если вы не задаете эту функцию, вы по-прежнему можете использовать это пошаговое руководство, но результаты могут отличаться от иллюстраций.

Пример на языке C#

При использовании примера C# в этом пошаговом руководстве также предполагается, что внешний код скрыт. Чтобы переключить отображение внешнего кода, щелкните правой кнопкой мыши заголовок таблицы Имя в окне Стек вызовов, а затем выберите или снимите флажок Показать внешний код. Если вы не задаете эту функцию, вы по-прежнему можете использовать это пошаговое руководство, но результаты могут отличаться от иллюстраций.

Пример C++

При использовании примера C++ можно игнорировать ссылки на внешний код в этой статье. Внешний код применяется только к примеру C#.

Иллюстрации

Иллюстрации, приведенные в этой статье, были записаны на четырёхъядерном компьютере с работающим примером на C#. Хотя для выполнения этого пошагового руководства можно использовать другие конфигурации, иллюстрации могут отличаться от того, что отображается на компьютере.

Создание примера проекта

Пример кода в этом пошаговом руководстве предназначен для приложения, которое ничего не делает. Цель упражнения — понять, как использовать окна инструментов для отладки параллельного приложения.

  1. Откройте Visual Studio и создайте проект.

    Если окно запуска не открыто, выберите Файл>Окно запуска.

    В окне запуска выберите новый проект.

    В окне запуска выберите Создать проект.

    В окне Создание нового проекта введите консоль в поле поиска. Затем выберите C#, C++или Visual Basic в списке языков, а затем выберите Windows из списка платформ.

    После применения фильтров языка и платформы выберите консольное приложение для .NET Core или C++, а затем нажмите кнопку "Далее".

    Замечание

    Если вы не видите правильный шаблон, перейдите к разделу "Сервис>получения инструментов и компонентов", который открывает установщик Visual Studio. Выберите компоненты разработки для .NET Desktop или разработки для Desktop с использованием C++, затем нажмите Изменить.

    В окне "Настройка нового проекта" введите имя или используйте имя по умолчанию в поле "Имя проекта ". Затем нажмите кнопку "Далее " или "Создать", независимо от того, какой параметр доступен.

    Для .NET Core выберите рекомендуемую целевую платформу или .NET 8, а затем выберите Создать.

    Появится новый проект консоли. После создания проекта появится исходный файл.

  2. Откройте файл кода .cpp, .cs или .vb в проекте. Удалите его содержимое, чтобы создать пустой файл кода.

  3. Вставьте следующий код для выбранного языка в пустой файл кода.

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Diagnostics;
    
    class S
    {
      static void Main()
      {
        pcount = Environment.ProcessorCount;
        Console.WriteLine("Proc count = " + pcount);
        ThreadPool.SetMinThreads(4, -1);
        ThreadPool.SetMaxThreads(4, -1);
    
        t1 = new Task(A, 1);
        t2 = new Task(A, 2);
        t3 = new Task(A, 3);
        t4 = new Task(A, 4);
        Console.WriteLine("Starting t1 " + t1.Id.ToString());
        t1.Start();
        Console.WriteLine("Starting t2 " + t2.Id.ToString());
        t2.Start();
        Console.WriteLine("Starting t3 " + t3.Id.ToString());
        t3.Start();
        Console.WriteLine("Starting t4 " + t4.Id.ToString());
        t4.Start();
    
        Console.ReadLine();
      }
    
      static void A(object o)
      {
        B(o);
      }
      static void B(object o)
      {
        C(o);
      }
      static void C(object o)
      {
        int temp = (int)o;
    
        Interlocked.Increment(ref aa);
        while (aa < 4)
        {
          ;
        }
    
        if (temp == 1)
        {
          // BP1 - all tasks in C
          Debugger.Break();
          waitFor1 = false;
        }
        else
        {
          while (waitFor1)
          {
            ;
          }
        }
        switch (temp)
        {
          case 1:
            D(o);
            break;
          case 2:
            F(o);
            break;
          case 3:
          case 4:
            I(o);
            break;
          default:
            Debug.Assert(false, "fool");
            break;
        }
      }
      static void D(object o)
      {
        E(o);
      }
      static void E(object o)
      {
        // break here at the same time as H and K
        while (bb < 2)
        {
          ;
        }
        //BP2 - 1 in E, 2 in H, 3 in J, 4 in K
        Debugger.Break();
        Interlocked.Increment(ref bb);
    
        //after
        L(o);
      }
      static void F(object o)
      {
        G(o);
      }
      static void G(object o)
      {
        H(o);
      }
      static void H(object o)
      {
        // break here at the same time as E and K
        Interlocked.Increment(ref bb);
        Monitor.Enter(mylock);
        while (bb < 3)
        {
          ;
        }
        Monitor.Exit(mylock);
    
    
        //after
        L(o);
      }
      static void I(object o)
      {
        J(o);
      }
      static void J(object o)
      {
        int temp2 = (int)o;
    
        switch (temp2)
        {
          case 3:
            t4.Wait();
            break;
          case 4:
            K(o);
            break;
          default:
            Debug.Assert(false, "fool2");
            break;
        }
      }
      static void K(object o)
      {
        // break here at the same time as E and H
        Interlocked.Increment(ref bb);
        Monitor.Enter(mylock);
        while (bb < 3)
        {
          ;
        }
        Monitor.Exit(mylock);
    
    
        //after
        L(o);
      }
      static void L(object oo)
      {
        int temp3 = (int)oo;
    
        switch (temp3)
        {
          case 1:
            M(oo);
            break;
          case 2:
            N(oo);
            break;
          case 4:
            O(oo);
            break;
          default:
            Debug.Assert(false, "fool3");
            break;
        }
      }
      static void M(object o)
      {
        // breaks here at the same time as N and Q
        Interlocked.Increment(ref cc);
        while (cc < 3)
        {
          ;
        }
        //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
        Debugger.Break();
        Interlocked.Increment(ref cc);
        while (true)
          Thread.Sleep(500); // for ever
      }
      static void N(object o)
      {
        // breaks here at the same time as M and Q
        Interlocked.Increment(ref cc);
        while (cc < 4)
        {
          ;
        }
        R(o);
      }
      static void O(object o)
      {
        Task t5 = Task.Factory.StartNew(P, TaskCreationOptions.AttachedToParent);
        t5.Wait();
        R(o);
      }
      static void P()
      {
        Console.WriteLine("t5 runs " + Task.CurrentId.ToString());
        Q();
      }
      static void Q()
      {
        // breaks here at the same time as N and M
        Interlocked.Increment(ref cc);
        while (cc < 4)
        {
          ;
        }
        // task 5 dies here freeing task 4 (its parent)
        Console.WriteLine("t5 dies " + Task.CurrentId.ToString());
        waitFor5 = false;
      }
      static void R(object o)
      {
        if ((int)o == 2)
        {
          //wait for task5 to die
          while (waitFor5) { ;}
    
    
          int i;
          //spin up all procs
          for (i = 0; i < pcount - 4; i++)
          {
            Task t = Task.Factory.StartNew(() => { while (true);});
            Console.WriteLine("Started task " + t.Id.ToString());
          }
    
          Task.Factory.StartNew(T, i + 1 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 2 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 3 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 4 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent); //scheduled
    
          //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died
          Debugger.Break();
        }
        else
        {
          Debug.Assert((int)o == 4);
          t3.Wait();
        }
      }
      static void T(object o)
      {
        Console.WriteLine("Scheduled run " + Task.CurrentId.ToString());
      }
      static Task t1, t2, t3, t4;
      static int aa = 0;
      static int bb = 0;
      static int cc = 0;
      static bool waitFor1 = true;
      static bool waitFor5 = true;
      static int pcount;
      static S mylock = new S();
    }
    

После обновления файла кода сохраните изменения и создайте решение.

  1. В меню File (Файл) выберите команду Save All (Сохранить все).

  2. В меню "Сборка" выберите "Перестроить решение".

Обратите внимание, что есть четыре вызова к Debugger.Break (DebugBreak в примере на C++). Поэтому вам не нужно добавлять точки останова. Простой запуск приложения приводит к сбоям в отладчике до четырех раз.

Использование окна параллельных стеков: представление потоков

Чтобы начать, в меню отладки нажмите кнопку "Начать отладку". Подождите, пока не будет достигнута первая точка останова.

Просмотр стека вызовов одного потока

  1. В меню отладки наведите указатель мыши на Windows и выберите "Потоки". Закрепите окно "Потоки" в нижней части Visual Studio.

  2. В меню Отладки наведите указатель мыши на Windows и выберите «Стек вызовов». Прикрепите окно стека вызовов в нижней части окна Visual Studio.

  3. Дважды щелкните поток в окне "Потоки ", чтобы сделать его текущим. Текущие потоки имеют желтую стрелку. При изменении текущего потока его стек вызовов отображается в окне стека вызовов .

Изучите окно параллельных стеков

В меню отладки наведите указатель на Windows и выберите Параллельные стеки. Убедитесь, что Threads выбраны в поле в левом верхнем углу.

С помощью окна Параллельных стеков можно одновременно просматривать несколько стеков вызовов в одном представлении. На следующем рисунке показано окно Параллельных стеков над окном стека вызовов .

Снимок экрана: вид

Отображение потоков в окне Параллельные Стеки

Стек вызовов основного потока отображается в одном поле, а стеки вызовов для остальных четырех потоков группируются в другом поле. Четыре потока группируются вместе, так как их кадры стека используют одинаковые контексты методов; то есть они находятся в одних и том же методах: A, Bи C. Чтобы просмотреть идентификаторы потоков и имена потоков, которые совместно используют одно и то же поле, наведите указатель мыши на поле с заголовком ([#] Потоки). Текущий поток отображается полужирным шрифтом.

Снимок экрана: подсказка с идентификаторами и именами потоков.

Подсказка, которая отображает идентификаторы и названия потоков

Желтая стрелка указывает активный кадр стека текущего потока.

Можно задать количество сведений для кадров стека (имена модулей, типы параметров, имена параметров, значения параметров, номера строк и смещения байтов), щелкнув правой кнопкой мыши в окне стека вызовов .

Синее выделение вокруг поля указывает, что текущий поток является частью этого поля. Текущий поток также обозначается полужирным кадром стека в подсказке. Если дважды щелкнуть основной поток в окне "Потоки", можно увидеть, что стрелка выделения в окне параллельных стеков перемещается соответствующим образом.

Снимок экрана: выделенный основной поток в окне Parallel Stacks.

Выделенный основной поток в окне Параллельных стеках

Возобновите выполнение до второй точки остановки

Чтобы возобновить выполнение до тех пор, пока не будет достигнута вторая точка останова, в меню отладки нажмите кнопку "Продолжить". На следующем рисунке показано дерево потоков на втором брейкпоинте.

Снимок экрана: окно Parallel Stacks с множеством ветвей.

Окно параллельных стеков, которое отображает множество ветвей

В первой точке останова четыре потока пошли через методы от S.A через S.B до S.C. Эта информация по-прежнему отображается в окне Параллельных стеков, но четыре потока продвинулись дальше. Один из них продолжал в S.D, а затем в S.E. Другой продолжал в S.F, S.G и S.H. Два других продолжали в S.I и S.J, и оттуда один из них пошел в S.K, а другой продолжал к внешнему коду без использования.

Вы можете навести указатель мыши на кадры стека, чтобы просмотреть ID потоков и другие сведения о кадрах. Синее выделение указывает текущий поток, а жёлтая стрелка показывает активную рамку стека текущего потока.

Вы можете навести указатель мыши на заголовок поля, например 1 поток или 2 потока, чтобы просмотреть идентификаторы потоков. Вы можете навести указатель мыши на кадры стека, чтобы просмотреть ID потоков и другие сведения о кадрах. Синее выделение указывает текущий поток, а жёлтая стрелка показывает активную рамку стека текущего потока.

Значок нитей ткани (переплетённые линии) указывает на активные кадры стека неактивных потоков. В окне стека вызовов дважды щелкните S.B, чтобы переключить кадры. Окно Parallel Stacks указывает текущий кадр стека текущего потока с помощью значка согнутой стрелки.

В окне "Потоки " переключитесь между потоками и обратите внимание, что представление в окне Параллельных стеков обновляется.

Вы можете переключиться на другой поток или в другой кадр другого потока, используя контекстное меню в окне Parallel Stacks . Например, щелкните правой кнопкой мыши S.J, наведите указатель на Переключить в кадр, а затем выберите команду.

Снимок экрана пути выполнения параллельных стеков.

Параллельные стеки: путь выполнения

Щелкните правой кнопкой мыши S.C и наведите указатель на Переключить на кадр. Одна из команд имеет флажок, указывающий кадр стека текущего потока. Вы можете переключиться на этот кадр того же потока (только изогнутая стрелка перемещается) или переключиться на другой поток (синее выделение также перемещается). На следующем рисунке показан подменю.

Скриншот меню Stacks с двумя опциями для C, в то время как J является активным.

Меню стека с 2 вариантами в C, пока J является текущим

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

Для больших приложений, имеющих много потоков, может потребоваться сосредоточиться только на подмножестве потоков. Окно Parallel Stacks может отображать стеки вызовов только для помеченных потоков. Чтобы пометить потоки, используйте контекстное меню или первую ячейку потока.

На панели инструментов нажмите кнопку "Показать только помеченные " рядом с полем списка.

Снимок экрана: окно parallel Stacks и подсказка.

Окно параллельных стеков и подсказка

Теперь в окне Parallel Stacks отображаются только помеченные потоки.

Возобновить выполнение до третьей точки останова

  1. Чтобы возобновить выполнение до тех пор, пока не будет достигнута третья точка останова, в меню отладки нажмите кнопку "Продолжить".

    Когда несколько потоков находятся в одном методе, но метод не находится в начале стека вызовов, он отображается в разных блоках. При текущей точке останова примером является S.L., который содержит три потока и появляется в трех полях. Дважды щелкните S.L.

    Снимок экрана: путь выполнения в окне Parallel Stacks.

    Путь выполнения в окне Parallel Stacks

    Обратите внимание, что S.L выделен полужирным шрифтом в двух других полях, чтобы вы могли видеть, где он еще появляется. Если вы хотите увидеть, какие кадры вызывают S.L и какие кадры вызываются S.L, нажмите кнопку Toggle Method View на панели инструментов. На следующем рисунке показано представление метода окна Parallel Stacks .

    Снимок экрана: представление метода в окне Parallel Stacks.

    Представление метода в окне Parallel Stacks

    Обратите внимание, как диаграмма сведена на выбранном методе и размещает ее в своем поле в середине представления. Звонящие и абоненты отображаются в верхней и нижней части соответственно. Нажмите кнопку "Переключить вид метода" снова, чтобы выйти из этого режима.

    Контекстное меню окна Parallel Stacks также содержит следующие другие элементы.

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

    • Параметры символов открывают соответствующие диалоговые окна.

    • Отображение потоков в исходном коде переключает отображение маркеров потока в исходном коде, которое показывает расположение потоков в исходном коде.

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

  2. В окне Параллельные стеки убедитесь, что на панели инструментов включена кнопка Автопрокрутка до текущего кадра стека.

    Когда у вас есть большие схемы и вы переходите к следующей точке останова, вы можете захотеть, чтобы представление автоматически прокручивалось до активного кадра стека текущего потока; то есть потока, который первым достиг точки останова.

  3. Прежде чем продолжить, в окне Параллельных стеков прокрутите весь путь влево и вниз.

Возобновить выполнение до четвёртого точки останова

  1. Чтобы возобновить выполнение до тех пор, пока не будет достигнута четвертая точка останова, в меню отладки нажмите кнопку "Продолжить".

    Обратите внимание на то, как вид был автоматически прокручен в нужное положение. Переключите потоки в окне "Потоки" или переключите кадры стека вызовов в окне "Стек вызовов" и обратите внимание на то, как окно всегда автоматически прокручивается к нужному кадру. Отключите параметр автоматической прокрутки до текущего кадра инструмента и просмотрите разницу.

    Представление с высоты птичьего полета также помогает с большими диаграммами в окне "Параллельные стеки". По умолчанию панорамный вид включен. Но вы можете переключить его, нажав кнопку между полосами прокрутки в правом нижнем углу окна, как показано на следующем рисунке.

    Снимок экрана панорамного вида в окне параллельных стеков.

    Общий вид в окне Параллельных стеков

    В виде с высоты птичьего полета вы можете переместить прямоугольник, чтобы быстро перемещаться по схеме.

    Другой способ перемещения схемы в любом направлении — выбрать пустую область схемы и перетащить ее в нужное место.

    Чтобы увеличить и выйти из схемы, нажмите и удерживайте клавиши CTRL при перемещении колесика мыши. Кроме того, нажмите кнопку "Масштаб" на панели инструментов, а затем используйте средство масштабирования.

    Вы также можете просматривать стеки в направлении сверху вниз, а не вниз, щелкнув меню "Сервис ", выбрав пункт "Параметры", а затем выберите или снимите флажок под узлом отладки .

  2. Прежде чем продолжить, в меню отладки выберите "Остановить отладку ", чтобы завершить выполнение.

Используйте окно параллельных задач и вид задач окна параллельных стеков

Перед продолжением мы рекомендуем выполнить предыдущие процедуры.

Перезапустите приложение до тех пор, пока не будет достигнута первая точка останова:

  1. В меню отладки выберите "Начать отладку " и подождите, пока будет достигнута первая точка останова.

  2. В меню отладки наведите указатель мыши на Windows и выберите "Потоки". Закрепите окно "Потоки" в нижней части Visual Studio.

  3. В меню Отладка выберите Windows, а затем «Стек вызовов». Закрепите окно стека вызовов внизу Visual Studio.

  4. Дважды щелкните поток в окне "Потоки ", чтобы сделать его текущим. Текущие потоки имеют желтую стрелку. При изменении текущего потока другие окна обновляются. Далее мы рассмотрим задачи.

  5. В меню отладки наведите указатель мыши на Windows и выберите "Задачи". На следующем рисунке показано окно "Задачи ".

    Снимок экрана: четыре выполняемых задачи в окне

    Четыре выполняемых задачи в окне

    Для каждой выполняемой задачи можно считывать её идентификатор, который возвращается одноимённым свойством, а также идентификатор и имя потока, который её выполняет, и её расположение (при наведении указателя мыши появляется подсказка со всем стеком вызовов). Кроме того, в столбце "Задача " можно увидеть метод, переданный в задачу; другими словами, начальная точка.

    Вы можете сортировать любой столбец. Обратите внимание на глиф сортировки, указывающий столбец сортировки и направление. Вы также можете изменить порядок столбцов, перетащив их влево или вправо.

    Желтая стрелка указывает текущую задачу. Вы можете переключать задачи, дважды щелкнув задачу или используя контекстное меню. При переключении задач базовый поток становится текущим, а другие окна обновляются.

    При ручном переключении из одной задачи в другую контур стрелки указывает текущий контекст отладчика для неактивной задачи.

    При ручном переходе с одной задачи на другую желтая стрелка перемещается, но белая стрелка по-прежнему отображает задачу, из-за которую отладчик прерывается.

Возобновите выполнение до второй точки остановки

Чтобы возобновить выполнение до тех пор, пока не будет достигнута вторая точка останова, в меню отладки нажмите кнопку "Продолжить".

Ранее столбец "Состояние " показал все задачи как активные, но теперь две задачи блокируются. Задачи могут быть заблокированы по различным причинам. В столбце "Состояние " наведите указатель мыши на задачу ожидания, чтобы узнать, почему она заблокирована. Например, на следующем рисунке задача 11 ожидает задачи 12.

Снимок экрана: две задачи ожидания в окне

Ранее столбец "Состояние " показал все задачи как активные, но теперь две задачи блокируются. Задачи могут быть заблокированы по различным причинам. В столбце "Состояние " наведите указатель мыши на задачу ожидания, чтобы узнать, почему она заблокирована. Например, на следующем рисунке задача 4 ожидает задачи 5.

Две задачи ожидания в окне

Задача 4, в свою очередь, ожидает монитора, принадлежащего потоку, назначенному задаче 2. (Щелкните правой кнопкой мыши строку заголовка и выберите "Столбцы>"Назначение потока для просмотра значения назначения потока для задачи 2).

Ожидание задач и подсказок в окне

Вы можете пометить задачу, щелкнув флаг в первом столбце окна "Задачи ".

С помощью тегов можно отслеживать задачи между разными точками останова в одном сеансе отладки или фильтровать задачи, стеки вызовов которых отображаются в окне Параллельных стеков .

Когда вы использовали окно Parallel Stacks ранее, вы просматривали потоки приложения. Снова просмотрите окно Параллельных стеков , но на этот раз просмотрите задачи приложения. Для этого выберите "Задачи " в поле в левом верхнем углу. На следующем рисунке показано представление задач.

Снимок экрана: вид

Представление задач в окне параллельных стеках

Потоки, которые в настоящее время не выполняются, не отображаются в представлении задач в окне параллельных стеках . Кроме того, для потоков, выполняющих задачи, некоторые кадры стека, которые не относятся к задачам, исключаются как из верхней, так и из нижней части стека.

Снова просмотрите окно "Задачи ". Щелкните правой кнопкой мыши любой заголовок столбца, чтобы просмотреть контекстное меню для столбца.

Контекстное меню можно использовать для добавления или удаления столбцов. Например, столбец AppDomain не выбран; поэтому он не отображается в списке. Выберите Parent. Родительский столбец отображается без значений для любой из четырех задач.

Возобновить выполнение до третьей точки останова

Чтобы возобновить выполнение до тех пор, пока не будет достигнута третья точка останова, в меню отладки нажмите кнопку "Продолжить".

Снимок экрана: представление

В этом примере обратите внимание, что задачи 11 и 12 выполняются в одном потоке (если столбец Назначение потока скрыт, покажите его). Эта информация не отображается в окне "Потоки"; видеть это здесь — еще одно преимущество окна "Задачи". Чтобы подтвердить это, просмотрите окно Параллельных стеков . Убедитесь, что вы просматриваете задачи. Задачи 11 и 12 можно найти, отсканировав подсказки в окне параллельных стеков .

Просмотр задач в окне параллельных стеков

Теперь выполняется новая задача, задача 5, и теперь ожидается задача 4. Вы можете увидеть, почему, наведя указатель мыши на ожидающую задачу в окне состояния. В столбце Родитель обратите внимание, что задача 4 является родительской задачей для задачи 5.

Чтобы лучше визуализировать отношения "родитель-дитя", щелкните правой кнопкой мыши строку заголовка столбца и выберите Представление отношений 'родитель-дитя'. Вы увидите следующую иллюстрацию.

Иерархическое представление в окне

Обратите внимание, что задачи 4 и 5 выполняются в одном потоке (покажите столбец Thread Assignment, если он скрыт). Эта информация не отображается в окне "Потоки"; видеть это здесь — еще одно преимущество окна "Задачи". Чтобы подтвердить это, просмотрите окно Параллельных стеков . Убедитесь, что вы просматриваете задачи. Найдите задачи 4 и 5, дважды щелкнув их в окне "Задачи ". Когда вы это делаете, синее выделение в окне Параллельных стеков обновляется. Вы также можете найти задачи 4 и 5, отсканировав подсказки в окне Параллельных стеков .

Просмотр задач в окне параллельных стеков

В окне Parallel Stacks щелкните правой кнопкой мыши S.P и выберите «Перейти к потоку». Окно переключается на режим отображения потоков, и соответствующий кадр становится видимым. Можно увидеть обе задачи в этой теме.

Выделенный поток в представлении потоков

Это еще одно преимущество вида задач в окне параллельных стеках по сравнению с окном потоков.

Возобновить выполнение до четвёртого точки останова

Чтобы возобновить выполнение до тех пор, пока не будет достигнута третья точка останова, в меню отладки нажмите кнопку "Продолжить". Выберите заголовок столбца идентификатора для сортировки по идентификатору. Вы увидите следующую иллюстрацию.

Снимок экрана: четыре состояния задачи в окне Параллельных стеков.

Задача 10 и задача 11 теперь ожидают друг друга и блокируются. Есть также несколько новых задач, которые теперь запланированы. Запланированные задачи — это задачи, которые были запущены в коде, но еще не выполняются. Поэтому в столбцах "Расположение " и "Назначение потока " отображаются сообщения по умолчанию или пустые.

Четыре состояния задач в окне параллельных стеков

Так как задача 5 завершена, она больше не отображается. Если на вашем компьютере это не так и взаимоблокировка не отображается, сделайте шаг, нажав клавишу F11.

Задача 3 и задача 4 теперь ожидают друг друга и блокируются. Есть также 5 новых задач, которые являются дочерними элементами задачи 2 и теперь запланированы. Запланированные задачи — это задачи, которые были запущены в коде, но еще не выполняются. Поэтому их столбцы Location и Thread Assignment пусты.

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

Подсказка заголовка в окне Parallel Stacks

Задачи можно сгруппировать по столбцу. В окне "Задачи " щелкните правой кнопкой мыши заголовок столбца состояния и выберите "Группировать по состоянию". На следующем рисунке показано окно "Задачи ", сгруппированное по состоянию.

Снимок экрана: сгруппированные задачи в окне

Сгруппированные задачи в окне

Вы также можете сгруппировать любой другой столбец. Сгруппируя задачи, можно сосредоточиться на подмножестве задач. Каждая сворачиваемая группа имеет счётчик элементов, сгруппированных вместе.

Последняя функция окна "Задачи " для проверки — контекстное меню, отображаемое при щелчке правой кнопкой мыши задачи.

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

Можно заморозить базовый поток задачи или задач или заморозить все потоки, кроме назначенного. Замороженный поток отображается в окне "Задачи, как и в окне "Потоки, синим значком приостановки.

Сводка

В этом пошаговом руководстве показаны окна отладки параллельных задач и параллельного стека. Используйте эти окна в реальных проектах, использующих многопоточный код. Можно изучить параллельный код, написанный на C++, C#или Visual Basic.