Пошаговое руководство. Отладка параллельного приложения
В этом пошаговом руководстве описывается использование окон Параллельные задачи и Параллельные стеки для отладки параллельного приложения. Эти окна помогают понять и проверить поведение во время выполнения кода, который использует Библиотека параллельных задач (TPL) или Среда выполнения с параллелизмом. Примеры кода, приведенные в этом пошаговом руководстве, имеют встроенные точки останова. Возможности диалоговых окон Параллельные задачи и Параллельные стеки показаны после прерывания выполнения программы.
В этом пошаговом руководстве рассматриваются следующие задачи:
Просмотр стеков вызовов всех потоков в одном представлении.
Просмотр списка экземпляров System.Threading.Tasks.Task, созданных в приложении.
Просмотр активных стеков вызовов задач вместо потоков.
Переход к коду из окон Параллельные стеки и Параллельные задачи.
Работа функций группирования, масштабирования и прочих связанных функций в окнах.
Обязательные компоненты
Предполагается, что режим Только мой код включен. В меню Сервис выберите пункт Параметры, разверните узел Отладка, выберите пункт Общие и затем установите флажок Включить режим "Только мой код" (только управляемый код). Если эта функция не задана, можно продолжить выполнение инструкций настоящего пошагового руководства, однако полученные результаты могут отличаться от описанных здесь.
Пример на языке C#
При использовании примера для C# предполагается, что внешний код является скрытым. Для переключения между режимами отображения внешнего кода щелкните правой кнопкой мыши заголовок столбца Имя окна Стек вызовов и затем установите или снимите флажок Показать внешний код. Если эта функция не задана, можно продолжить выполнение инструкций настоящего пошагового руководства, однако полученные результаты могут отличаться от описанных здесь.
Пример на языке C++
При использовании примера для языка C++ можно пропускать ссылки на внешний код, приведенные в данном разделе. Внешний код применяется только к примеру для языка C#.
Рисунки
Рисунки, приведенные в настоящем разделе, получены на двухъядерном компьютере, на котором выполняется пример C#. Для выполнения инструкций настоящего пошагового руководства можно использовать другие конфигурации, однако диалоговые окна могут отличаться от приведенных в руководстве.
Создание примера проекта
Пример кода, приведенный в данном пошаговом руководстве, предназначен для приложения, которое не выполняет никаких действий. Цель данного примера кода: понять использование окон инструментов для отладки параллельного приложения.
Создание примера проекта
В меню Файл окна Visual Studio выберите команду Создать и щелкните Проект.
В области Установленные шаблоны выберите Visual C#, Visual Basic или Visual C++. При использовании управляемых языков убедитесь, что в окне среды отображается .NET Framework 4.
Выберите Консольное приложение, а затем нажмите кнопку ОК. Оставайтесь в конфигурации отладки, которая используется по умолчанию.
Откройте CPP-файл, CS-файл или VB-файл кода в проекте. Удалите его содержимое, чтобы создать пустой файл кода.
Вставьте в пустой файл кода следующий код для данного языка.
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Diagnostics
Module S
Sub Main()
pcount = Environment.ProcessorCount
Console.WriteLine("Proc count = " + pcount.ToString())
ThreadPool.SetMinThreads(4, -1)
ThreadPool.SetMaxThreads(4, -1)
t1 = New Task(AddressOf A, 1)
t2 = New Task(AddressOf A, 2)
t3 = New Task(AddressOf A, 3)
t4 = New Task(AddressOf 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()
End Sub
Sub A(ByVal o As Object)
B(o)
End Sub
Sub B(ByVal o As Object)
C(o)
End Sub
Sub C(ByVal o As Object)
Dim temp As Integer = o
Interlocked.Increment(aa)
While (aa < 4)
End While
If (temp = 1) Then
' BP1 - all tasks in C
Debugger.Break()
waitFor1 = False
Else
While (waitFor1)
End While
End If
Select Case temp
Case 1
D(o)
Case 2
F(o)
Case 3, 4
I(o)
Case Else
Debug.Assert(False, "fool")
End Select
End Sub
Sub D(ByVal o As Object)
E(o)
End Sub
Sub E(ByVal o As Object)
' break here at the same time as H and K
While (bb < 2)
End While
'BP2 - 1 in E, 2 in H, 3 in J, 4 in K
Debugger.Break()
Interlocked.Increment(bb)
'after
L(o)
End Sub
Sub F(ByVal o As Object)
G(o)
End Sub
Sub G(ByVal o As Object)
H(o)
End Sub
Sub H(ByVal o As Object)
' break here at the same time as E and K
Interlocked.Increment(bb)
Monitor.Enter(mylock)
While (bb < 3)
End While
Monitor.Exit(mylock)
'after
L(o)
End Sub
Sub I(ByVal o As Object)
J(o)
End Sub
Sub J(ByVal o As Object)
Dim temp2 As Integer = o
Select Case temp2
Case 3
t4.Wait()
Case 4
K(o)
Case Else
Debug.Assert(False, "fool2")
End Select
End Sub
Sub K(ByVal o As Object)
' break here at the same time as E and H
Interlocked.Increment(bb)
Monitor.Enter(mylock)
While (bb < 3)
End While
Monitor.Exit(mylock)
'after
L(o)
End Sub
Sub L(ByVal oo As Object)
Dim temp3 As Integer = oo
Select Case temp3
Case 1
M(oo)
Case 2
N(oo)
Case 4
O(oo)
Case Else
Debug.Assert(False, "fool3")
End Select
End Sub
Sub M(ByVal o As Object)
' breaks here at the same time as N and Q
Interlocked.Increment(cc)
While (cc < 3)
End While
'BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
Debugger.Break()
Interlocked.Increment(cc)
While (True)
Thread.Sleep(500) ' for ever
End While
End Sub
Sub N(ByVal o As Object)
' breaks here at the same time as M and Q
Interlocked.Increment(cc)
While (cc < 4)
End While
R(o)
End Sub
Sub O(ByVal o As Object)
Dim t5 As Task = Task.Factory.StartNew(AddressOf P, TaskCreationOptions.AttachedToParent)
t5.Wait()
R(o)
End Sub
Sub P()
Console.WriteLine("t5 runs " + Task.CurrentId.ToString())
Q()
End Sub
Sub Q()
' breaks here at the same time as N and M
Interlocked.Increment(cc)
While (cc < 4)
End While
' task 5 dies here freeing task 4 (its parent)
Console.WriteLine("t5 dies " + Task.CurrentId.ToString())
waitFor5 = False
End Sub
Sub R(ByVal o As Object)
If (o = 2) Then
' wait for task5 to die
While waitFor5
End While
'//spin up all procs
Dim i As Integer
For i = 0 To pcount - 4 - 1
Dim t As Task = Task.Factory.StartNew(Sub()
While True
End While
End Sub)
Console.WriteLine("Started task " + t.Id.ToString())
Next
Task.Factory.StartNew(AddressOf T, i + 1 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
Task.Factory.StartNew(AddressOf T, i + 2 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
Task.Factory.StartNew(AddressOf T, i + 3 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
Task.Factory.StartNew(AddressOf T, i + 4 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
Task.Factory.StartNew(AddressOf 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(o = 4)
t3.Wait()
End If
End Sub
Sub T(ByVal o As Object)
Console.WriteLine("Scheduled run " + Task.CurrentId.ToString())
End Sub
Private t1, t2, t3, t4 As Task
Private aa As Integer = 0
Private bb As Integer = 0
Private cc As Integer = 0
Private waitFor1 As Boolean = True
Private waitFor5 As Boolean = True
Private pcount As Integer
Private mylock As New S2()
End Module
Public Class S2
End Class
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();
}
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <ppl.h>
#include <agents.h>
#include <stdio.h>
#include <concrtrm.h>
#include <vector>
CRITICAL_SECTION cs;
using namespace ::std;
using namespace ::std::tr1;
using namespace ::Concurrency;
task_group task4;
task_group task3;
task_group task2;
volatile long aa = 0;
volatile long bb = 0;
volatile long cc = 0;
static bool waitFor1 = true;
static bool waitFor5 = true;
#pragma optimize("", off)
void Spin()
{
for(int i=0;i<50*50000;++i);
}
#pragma optimize("",on)
template<class Func>
class RunFunc
{
Func& m_Func;
int m_o;
public:
RunFunc(Func func,int o):m_Func(func),m_o(o){
};
void operator()()const{
m_Func(m_o);
};
};
void T(int o)
{
cout << "Scheduled run \n";
};
void R(int o)
{
if (o == 2)
{
while (waitFor5) { ;}
Spin();
//use up all processors but 4 by scheduling 4 non-terminating tasks.
int numProcsToBurn = GetProcessorCount() - 4;
int i;
vector<call<int>*> tasks;
for (i = 0; i < numProcsToBurn; i++)
{
tasks.push_back(new call<int>([](int i){while(true)Spin();}));
asend(tasks[i],1);
cout << "Started task \n";
}
task_handle<RunFunc<decltype(T)>> t6(RunFunc<decltype(T)>(T,i + 1 + 5));
task_handle<RunFunc<decltype(T)>> t7(RunFunc<decltype(T)>(T,i + 2 + 5));
task_handle<RunFunc<decltype(T)>> t8(RunFunc<decltype(T)>(T,i + 3 + 5));
task_handle<RunFunc<decltype(T)>> t9(RunFunc<decltype(T)>(T,i + 4 + 5));
task_handle<RunFunc<decltype(T)>> t10(RunFunc<decltype(T)>(T,i + 5 + 5));
task2.run(t6);
task2.run(t7);
task2.run(t8);
task2.run(t9);
task2.run(t10);
//BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died
DebugBreak();
}
else
{
if (o!=4)
throw;
task3.wait();
}
};
void Q()
{
// breaks here at the same time as N and M
InterlockedIncrement(& cc);
while (cc < 4)
{
;
}
// task 5 dies here freeing task 4 (its parent)
cout << "t5 dies\n";
waitFor5 = false;
};
void P()
{
cout << "t5 runs\n";
Q();
};
void O(int o)
{
task_group t5;
t5.run(&P);
t5.wait();
R(o);
};
void N(int o)
{
// breaks here at the same time as M and Q
InterlockedIncrement(&cc);
while (cc < 4)
{
;
}
R(o);
};
void M(int o)
{
// breaks here at the same time as N and Q
InterlockedIncrement(&cc);
while (cc < 3)
{
;
}
//BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
DebugBreak();
InterlockedIncrement(&cc);
while (true)
Sleep(500); // for ever
};
void L(int oo)
{
int temp3 = oo;
switch (temp3)
{
case 1:
M(oo);
break;
case 2:
N(oo);
break;
case 4:
O(oo);
break;
default:
throw; //fool3
break;
}
}
void K(int o)
{
// break here at the same time as E and H
InterlockedIncrement(&bb);
EnterCriticalSection(&cs);
while (bb < 3)
{
;
}
LeaveCriticalSection(&cs);
Spin();
//after
L(o);
}
void J(int o)
{
int temp2 = o;
switch (temp2)
{
case 3:
task4.wait();
break;
case 4:
K(o);
break;
default:
throw; //fool2
break;
}
}
static void I(int o)
{
J(o);
}
static void H(int o)
{
// break here at the same time as E and K
InterlockedIncrement(&bb);
EnterCriticalSection(&cs);
while (bb < 3)
{
;
}
LeaveCriticalSection(&cs);
Spin();
//after
L(o);
}
static void G(int o)
{
H(o);
}
static void F(int o)
{
G(o);
}
static void E(int 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
Spin(); // for native case only
DebugBreak();
InterlockedIncrement(&bb);
//after
L(o);
}
static void D(int o)
{
E(o);
}
static void C(int o)
{
int temp = o;
InterlockedIncrement(&aa);
while (aa < 4)
{
;
}
if (temp == 1)
{
// BP1 - all tasks in C
DebugBreak();
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:
throw; //fool
break;
}
}
static void B(int o)
{
C(o);
}
void A(int o)
{
B(o);
}
int main()
{
InitializeCriticalSection(&cs);
task_group tasks;
task_handle<RunFunc<decltype(A)>> t1(RunFunc<decltype(A)>(A,1));
tasks.run(t1);
task_handle<RunFunc<decltype(A)>> t2(RunFunc<decltype(A)>(A,2));
task2.run(t2);
task_handle<RunFunc<decltype(A)>> t3(RunFunc<decltype(A)>(A,3));
task3.run(t3);
task_handle<RunFunc<decltype(A)>> t4(RunFunc<decltype(A)>(A,4));
task4.run(t4);
getchar();
return 1;
}
В меню Файл выберите команду Сохранить все.
Выберите пункт Перестроить решение в меню Построение.
Обратите внимание, что имеются четыре вызова Debugger.Break (DebugBreak в примере C++). Следовательно, установка точек останова не требуется. При выполнении приложения отладчик будет вызываться до 4 раз.
Использование окна "Параллельные стеки". Представление "Потоки"
В меню Отладка щелкните Начать отладку. Дождитесь попадания в первую точку останова.
Просмотр стека вызовов одного потока
В меню Отладка выберите пункт Окна и затем щелкните Потоки. Закрепите окно Потоки в верхней части окна Visual Studio.
В меню Отладка наведите указатель на пункт Окна и выберите команду Стек вызовов. Закрепите окно Стек вызовов в верхней части окна Visual Studio.
Дважды щелкните поток в окне Потоки, чтобы сделать его текущим. Рядом с текущими потоками отображается желтая стрелка. При изменении текущего потока его стек вызовов отображается в окне Стек вызовов.
Изучение окна "Параллельные стеки"
В меню Отладка наведите указатель на пункт Окна и затем выберите пункт Параллельные стеки. Убедитесь, что в верхнем левом углу выбрано значение Потоки.
С помощью окна Параллельные стеки можно одновременно просматривать несколько стеков вызовов в одном представлении. На следующем рисунке показано окно Параллельные стеки над окном Стек вызовов.
Стек вызовов главного потока отображается в одном окне, а стеки вызовов для других четырех потоков сгруппированы в другом окне. Четыре потока сгруппированы вместе, поскольку их кадры стека совместно используют одни и те же контексты методов: A, B и C. Чтобы просмотреть идентификаторы и имена потоков, которые совместно используют одно окно, наведите указатель мыши на заголовок (4 потока) Текущий поток выделен полужирным шрифтом, как показано на следующем рисунке.
Желтая стрелка указывает активный кадр стека текущего потока. Чтобы получить более подробные сведения, наведите указатель мыши.
Чтобы задать объем сведений, отображаемых для кадров стеков (Имена модулей, Типы параметров, Имена параметров, Значения параметров, Номера строк и Смещение в байтах), щелкните правой кнопкой мыши в окне Стек вызовов.
Синяя рамка вокруг окна указывает, что текущий поток является частью этого окна. Текущий поток также обозначается полужирным шрифтом в подсказке для кадра стека. Если дважды щелкнуть главный поток в окне "Потоки", синяя рамка в окне Параллельные стеки переместится на поле, содержащее этот поток.
Возобновление выполнения до второй точки останова
Чтобы возобновить выполнение до попадания во вторую точку останова, в меню Отладка выберите пункт Продолжить. На следующем рисунке показано дерево потоков для второй точки останова.
В первой точке останова все четыре потока были получены от методов 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, чтобы переключить кадры. Текущий кадр стека текущего потока показан в окне Параллельные стеки с помощью зеленой круговой стрелки.
В окне Потоки переключитесь между потоками и обратите внимание, что представление в окне Параллельные стеки обновилось.
Для переключения на другой поток или кадр другого потока используйте команды контекстного меню в окне Параллельные стеки. Например, щелкните правой кнопкой мыши S.J, выберите команду Перейти к кадру и затем выберите команду.
Щелкните правой кнопкой мыши S.C и выберите команду Перейти к кадру. Напротив одной из команд установлен флажок, который указывает кадр стека текущего потока. Можно переключиться в кадр того же потока (зеленая стрелка будет перемещена) или можно переключиться в другой поток (синяя рамка также будет перемещена). На следующем рисунке показаны команды вложенного меню.
Если контекст метода связан только с одним кадром стека, отображается заголовок окна 1 поток. Для переключения в кадр стека дважды щелкните его. При двойном щелчке контекста метода, имеющего более 1 кадра стека, связанного с ним, автоматически откроется всплывающее меню. При наведении указателя мыши на контексты методов справа отображается черный треугольник. Контекстное меню также отображается при щелчке этого треугольника.
В больших приложениях, имеющих множество потоков, можно выделить подмножество потоков. В окне Параллельные стеки могут отображаться стеки вызовов только для отмеченных потоков. На панели инструментов нажмите кнопку Показать только отмеченные, расположенную рядом со списком.
В окне Потоки по очереди отметьте потоки, чтобы просмотреть их стеки вызовов в окне Параллельные стеки. Чтобы отметить потоки, используйте команды контекстного меню или первую ячейку потока. Чтобы отобразить все потоки, нажмите кнопку Показать только отмеченные еще раз.
Возобновление выполнения до третьей точки останова
Чтобы возобновить выполнение до попадания в третью точку останова, в меню Отладка выберите пункт Продолжить.
Если в одном методе присутствует несколько потоков, но метод находится не в начале стека вызовов, метод отображается в отдельных окнах. В примере текущей точкой останова является S.L, которая имеет три потока и отображается в трех окнах. Дважды щелкните точку останова S.L.
Обратите внимание, что S.L выделена полужирным шрифтом в других двух окнах для облегчения поиска. Если необходимо просмотреть, какие кадры вызываются в S.L, а также какие кадры вызывает она, нажмите кнопку Представление метода, расположенную на панели инструментов. На следующем рисунке показано представление методов окна Параллельные стеки.
Обратите внимание, как схема расположена на выбранном методе, а также расположение метода в собственном окне схемы в середине представления. В верхней части окна отображается вызываемый и вызывающий метод. Нажмите кнопку Представление метода еще раз, чтобы отключить этот режим.
В контекстном меню окна Параллельные стеки также представлены следующие элементы.
Шестнадцатеричный вывод — переключение между десятичным и шестнадцатеричным представлением чисел в подсказке.
Сведения о загрузке символов и Параметры символов — открытие соответствующих диалоговых окон.
К исходному коду и К дизассемблированному коду — переход в редактор для выбранного метода.
Показать внешний код — отображение всех кадров, даже не используемых в пользовательском коде. Выберите эту команду, чтобы увидеть как расширяется схема для отображения дополнительных кадров (кадры могут быть затемнены, поскольку для них отсутствуют символы).
При наличии объемной схемы и переходе к следующей точке останова может потребоваться автоматическая прокрутка активных кадров стека текущего потока, т. е. просмотр потока, который первым достигает точки останова. В окне Параллельные стеки убедитесь, что кнопка Автопрокрутка к текущему кадру стека, расположенная на панели инструментов, нажата.
Прежде чем продолжить, в окне Параллельные стеки прокрутите схему влево и вниз.
Возобновление выполнения до четвертой точки останова
Чтобы возобновить выполнение до попадания в четвертую точку останова, в меню Отладка выберите пункт Продолжить.
Обратите внимание, как представление автоматически прокрутило схему в требуемое расположение. Переключите потоки в окне Потоки или переключите кадры стека в окне Стек вызовов и обратите внимание, как представление всегда автоматически выполняет прокрутку к требуемому кадру. Отключите функцию Автопрокрутка к текущему кадру стека и просмотрите отличие.
При наличии больших схем в окне Параллельные стеки также удобно воспользоваться функцией Вид с высоты птичьего полета. Для просмотра Вида с высоты птичьего полета нажмите кнопку, расположенную между полосами прокрутки в нижнем правом углу окна, как показано на следующем рисунке.
Для быстрого перемещения по схеме переместите прямоугольник в требуемую область.
Другой способ перемещения схемы в любом направлении: щелкните по пустой области и перетащите схему в требуемое место.
Чтобы увеличить или уменьшить схему, нажав и удерживая клавишу CTRL, прокрутите колесико мыши. Другой способ: на панели инструментов нажмите кнопку "Увеличить" и затем воспользуйтесь средством "Увеличить".
Чтобы просмотреть стеки в направлении сверху вниз вместо направления снизу вверх, в меню Сервис выберите пункт Параметры и затем установите или снимите флажок в узле Отладка.
Прежде чем продолжить в меню Отладка выберите командуОстановить отладку, чтобы завершить выполнение.
Использование окна "Параллельные задачи" и представления "Задачи" окна "Параллельные стеки"
Прежде чем продолжить, рекомендуется завершить ранее начатые процедуры.
Перезапуск приложения до попадания в первую точку останова
В меню Отладка выберите команду Начать отладку и дождитесь попадания в первую точку отладки.
В меню Отладка выберите пункт Окна и затем щелкните Потоки. Закрепите окно Потоки в верхней части окна Visual Studio.
В меню Отладка наведите указатель на пункт Окна и выберите команду Стек вызовов. Закрепите окно Стек вызовов в верхней части окна Visual Studio.
Дважды щелкните поток в окне Потоки, чтобы сделать его текущим. Рядом с текущими потоками должна отображаться желтая стрелка. При изменении текущего потока сведения в других окнах обновляются. Далее изучим содержимое окна "Параллельные задачи".
В меню Отладка наведите указатель на пункт Окна и затем выберите пункт Параллельные задачи. На следующем рисунке показано окно Параллельные задачи.
Для каждой запущенной задачи можно увидеть ее идентификатор, который возвращен свойством с тем же самым именем, имя потока, который запускает задача, ее расположение (при наведении указателя мыши отображается подсказка, у которой есть целый стек вызовов). Кроме того, в столбце Задача отображается метод, который передан в задачу, или иными словами, точка запуска.
Любой столбец можно отсортировать. Обратите внимание на глиф сортировки, который указывает столбец и направление сортировки. Расположение столбцов можно изменять, перетаскивая их вправо или влево.
Рядом с текущей задачей отображается желтая стрелка. Чтобы переключить задачи, дважды щелкните задачу или воспользуйтесь командной контекстного меню. При переключении задач потоки, расположенные ниже, становятся текущими, а сведения в других окнах обновляются.
При переключении с одной задачи на другую вручную желтая стрелка перемещается, но при этом белая стрелка по-прежнему показывает задачу, которая привела к прерыванию работы отладчика.
Возобновление выполнения до второй точки останова
Чтобы возобновить выполнение до попадания во вторую точку останова, в меню Отладка выберите пункт Продолжить.
Ранее в столбце Состояние все задачи отображались как запущенные, но теперь две задачи отображаются как ожидающие. Задачи могут быть заблокированы по разным причинам. Чтобы узнать причину блокирования задачи, наведите указатель мыши на задачу в столбце Состояние. Например, на следующем рисунке задача 3 ожидает задачу 4.
Задача 4, в свою очередь, ожидает монитор, принадлежащий потоку, назначенному задаче 2.
Можно пометить задачу, щелкнув значок флага в первом столбце окна Параллельные задачи.
Отметки с помощью флагов можно использовать для отслеживания задач между различными точками останова в одном сеансе отладки или для фильтрации задач, у которых стеки вызовов отображаются в окне Параллельные стеки.
Ранее в окне Параллельные стеки отображались потоки приложения. Если сейчас открыть окно Параллельные стеки, то в нем уже будет отображаться представление времени задач приложения. В списке, расположенном в верхнем левом углу, выберите пункт Задачи. На следующем рисунке показано представление "Задачи".
Потоки, которые в настоящий момент не выполняют задачи, не отображаются в представлении "Задачи" окна Параллельные стеки. Кроме того, для потоков, которые выполняют задачи, некоторые из кадров стека, которые не относятся к задачам, отфильтрованы от верхней и нижней границы стека.
Откройте окно Параллельные задачи еще раз. Щелкните правой кнопкой мыши заголовок любого столбца, чтобы открыть контекстное меню для столбца.
Контекстное меню можно использовать для удаления или добавления столбцов. Например, столбец AppDomain не выбран, поэтому он не отображается в списке. Щелкните Родитель. В столбце Родитель не отображаются значения ни для одной из четырех задач.
Возобновление выполнения до третьей точки останова
Чтобы возобновить выполнение до попадания в третью точку останова, выберите пункт Продолжить в меню Отладка.
Теперь запускается новая задача под номером 5, а задача 4 переходит в режим ожидания. Для просмотра причины перехода в режим ожидания наведите указатель мыши на задачу в окне Состояние. В столбце Родитель обратите внимание, что задача 4 является родительской по отношению к задаче 5.
Чтобы получить более наглядное представление связи "родитель-потомок", щелкните правой кнопкой мыши заголовок столбца Родитель и затем выберите пункт Представление родительского и дочернего объектов. Должно отображаться такое же окно, как на следующем рисунке.
Обратите внимание, что задачи 4 и 5 запущены в одном потоке. Эти сведения отображаются не в окне Потоки, а в окне Параллельные задачи, что является преимуществом данного окна. Чтобы проверить это, откройте окно Параллельные стеки. Убедитесь, что в качестве представления выбран пункт Задачи. Определите расположение задач 4 и 5, дважды щелкнув их в окне Параллельные задачи. После выполнения этого действия синяя граница в окне Параллельные стеки будет обновлена. Кроме того, расположение задач 4 и 5 можно определить путем сканирования подсказок в окнеПараллельные стеки.
В окне Параллельные стеки щелкните правой кнопкой мыши S.P и затем выберите команду Перейти к потоку. Окно переключается в представление "Потоки" и соответствующий кадр в представлении. В одном потоке можно увидеть обе задачи.
Это является еще одним преимуществом представления "Задачи" в окне Параллельные стеки по сравнению с окном Потоки.
Возобновление выполнения до четвертой точки останова
Чтобы возобновить выполнение до попадания в третью точку останова, выберите пункт Продолжить в меню Отладка. Щелкните заголовок столбца ИД, чтобы выполнить сортировку по идентификатору. Должно отображаться такое же окно, как на следующем рисунке.
Поскольку задача 5 уже завершена, она больше не отображается. Если на экране компьютера не отображается взаимоблокировка, перейдите на другой шаг, нажав клавишу F11.
Задачи 3 и 4 ожидают друг друга и являются взаимоблокированными. 5 новых задач являются дочерними по отношению к задаче 2 и в данный момент запланированы. Запланированными задачами являются задачи, которые были запущены в коде, но еще не запущены на компьютере. Поэтому для этих задач значения в столбцах Расположение и Назначение потоков отсутствуют.
Просмотрите окно Параллельные стеки еще раз. Для заголовка каждого окна есть подсказка, в которой отображаются идентификаторы и имена потоков. Переключитесь в представление "Задачи" в окне Параллельные стеки. Наведите указатель мыши на заголовок, чтобы просмотреть идентификатор и имя задачи, а также состояние задачи, как показано на следующем рисунке.
Можно сгруппировать задачи по столбцу. В окне Параллельные задачи щелкните правой кнопкой мыши заголовок столбца Состояние и затем выберите команду Сгруппировать по состоянию. На следующем рисунке показано окно Параллельные задачи, сгруппированное по состоянию.
Задачи можно группировать по любому другому столбцу. Группировка задач позволяет сосредоточиться на подмножестве задач. Каждая разворачиваемая группа имеет счетчик сгруппированных элементов. Чтобы быстро снять отметку всех элементов в группе, нажмите кнопку Отметить, расположенную справа от кнопки Свернуть.
Последняя изучаемая функция окна Параллельные задачи — контекстное меню, которое отображается при щелчке правой кнопкой мыши задачи.
В зависимости от состояния задачи в контекстном меню отображаются различные команды. В контекстном меню могут отображаться следующие команды: Копировать, Выделить все, Шестнадцатеричный вывод, Переключение на задачу, Зафиксировать назначенный поток, Зафиксировать все потоки, кроме этого, Поток, назначенный разморозке и Отметить.
Можно зафиксировать поток задачи или задачи, либо зафиксировать все потоки, за исключением назначенного. Зафиксированный поток отображается в окне Параллельные задачи так же, как в окне Потоки с помощью синего значка пауза.
Сводка
В этом пошаговом руководстве описаны окна Параллельные задачи и Параллельные стеки. Используйте эти окна в проектах, в которых используется многопоточный код. Можно проверить параллельный код, написанный на языке C++, C# или Visual Basic.
См. также
Задачи
Пошаговое руководство. Отладка параллельного приложения
Основные понятия
Среда выполнения с параллелизмом
Использование окна "Параллельные стеки"