Пошаговое руководство. Отладка параллельного приложения в 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 Installer. Выберите рабочую нагрузку Разработка классических приложений .NET или Разработка классических приложений на 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. В меню Сборка выберите Собрать решение повторно.

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

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

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

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

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

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

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

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

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

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

Screenshot of Threads view in Parallel Stacks window.

Threads view in Parallel Stacks window

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

Screenshot of Tooltip that shows thread IDs and names.

Tooltip that shows thread IDs and names

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

Чтобы задать объем сведений, отображаемых для кадров стеков (Имена модулей, Типы параметров, Имена параметров, Значения параметров, Номера строк и Смещение в байтах), щелкните правой кнопкой мыши в окне Стек вызовов.

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

Screenshot of Highlighted main thread in Parallel Stacks window.

Highlighted main thread in Parallel Stacks window

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

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

Screenshot of Parallel Stacks window that shows many branches.

Parallel Stacks window that shows many branches

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

Чтобы узнать идентификаторы и другие сведения о кадре, наводите указатель мыши на кадры стека. Синяя рамка указывает текущий поток, а желтая стрелка — активный кадр стека текущего потока.

Чтобы просмотреть идентификаторы потоков, наведите указатель мыши на заголовок окна, например 1 поток или 2 потока. Чтобы узнать идентификаторы и другие сведения о кадре, наводите указатель мыши на кадры стека. Синяя рамка указывает текущий поток, а желтая стрелка — активный кадр стека текущего потока.

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

Примечание.

Описание всех значков в окне Параллельных стеков см. в разделе "Использование окна параллельных стеков".

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

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

Screenshot of Parallel Stacks Path of Execution.

Parallel Stacks Path of Execution

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

Screenshot of Stacks menu with 2 options on C while J is current.

Stacks menu with 2 options on C while J is current

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

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

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

Screenshot of Parallel Stacks window and tooltip.

Parallel Stacks window and tooltip

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

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

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

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

    Screenshot of Execution path in Parallel Stacks window.

    Execution path in Parallel Stacks window

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

    Screenshot of Method view in Parallel Stacks window.

    Method view in Parallel Stacks window

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

    В контекстном меню окна Параллельные стеки также представлены следующие элементы.

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

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

    • Показать потоки в исходном коде — переключение режима отображения меток потоков в исходном коде для просмотра расположения потоков в нем.

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

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

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

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

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

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

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

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

    Screenshot of Birds eye view in Parallel Stacks window.

    Bird's-eye view in Parallel Stacks window

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

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

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

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

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

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

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

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

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

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

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

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

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

    Screenshot of Four running tasks in Tasks window.

    Four running tasks in Tasks window

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

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

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

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

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

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

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

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

Screenshot of Two waiting tasks in Tasks window.

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

Two waiting tasks in Tasks window

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

Waiting task and tooltip in Tasks window

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

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

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

Screenshot of Tasks view in Parallel Stacks window.

Tasks view in Parallel Stacks window

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

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

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

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

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

Screenshot of Parent-child view in Tasks window.

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

Task view in Parallel Stacks window

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

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

Parent-child view in Tasks window

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

Task view in Parallel Stacks window

В окне параллельных стеков щелкните правой кнопкой мыши S.P и выберите "Перейти к потоку". Окно переключается в представление "Потоки" и соответствующий кадр в представлении. В одном потоке можно увидеть обе задачи.

Highlighted thread in threads view

Это является еще одним преимуществом представления "Задачи" в окне Параллельные стеки по сравнению с окном Потоки.

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

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

Screenshot of Four task states in Parallel Stacks window.

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

Four task states in Parallel Stacks window

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

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

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

Header tooltip in Parallel Stacks window

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

Screenshot of Grouped tasks in Tasks window.

Grouped tasks in Tasks window

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

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

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

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

Итоги

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