逐步解說:偵錯平行應用程式
本逐步解說顯示如何使用 [平行工作] 和 [平行堆疊] 視窗來偵錯平行應用程式。 這些視窗協助您了解和確認使用工作平行程式庫或並行執行階段之程式碼的執行階段行為。 本逐步解說提供具有內建中斷點的範例程式碼。 在程式碼中斷之後,本逐步解說會顯示如何使用 [平行工作] 和 [平行堆疊] 視窗來檢查程式碼。
本逐步解說教導下列工作:
如何在一個檢視中檢視所有執行緒的呼叫堆疊。
如何檢視應用程式中建立之 System.Threading.Tasks.Task 執行個體的清單。
如何檢視工作而非執行緒的實際呼叫堆疊。
如何從 [平行工作] 和 [平行堆疊] 視窗巡覽至程式碼。
視窗如何透過分組、縮放和其他相關功能來處理比例調整。
必要條件
您的電腦中必須安裝 Visual Studio 2010。
本逐步解說假設 [Just My Code] 已啟用。 按一下 [工具] 功能表上的 [選項],展開 [偵錯] 節點,再選取 [一般],然後選取 [啟用 Just My Code (僅限 Managed)。 如果未設定這項功能,您仍然可以使用本逐步解說,但結果可能與插圖不同。
C# 範例
如果您使用 C# 範例,本逐步解說也會假設外部程式碼已隱藏。 若要切換是否顯示外部程式碼,請以滑鼠右鍵按一下 [呼叫堆疊] 視窗的 [名稱] 表格標題,然後選取或清除 [顯示外部程式碼]。 如果未設定這項功能,您仍然可以使用本逐步解說,但結果可能與插圖不同。
C++ 範例
如果您使用 C++ 範例,則可以忽略本主題中對於外部程式碼的引述。 外部程式碼只適用於 C# 範例。
插圖
本主題中的插圖是在執行 C# 範例的四核心電腦上錄製。 雖然您可以使用其他組態來完成本逐步解說,但插圖可能與您電腦上所呈現的畫面不同。
建立範例專案
本逐步解說中的範例程式碼適用於不執任何動作的應用程式。 目的只是要了解如何使用工具視窗來偵錯平行應用程式。
建立範例專案
在 Visual Studio 的 [檔案] 功能表上,指向 [新增],然後按一下 [專案]。
在 [已安裝的範本] 窗格中,選取 [Visual C#]、[Visual Basic] 或 [Visual C++]。 至於 Managed 語言,請確定架構方塊中有顯示 .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 (C++ 範例中的 DebugBreak) 的呼叫有四個,因此,您不需要插入中斷點,只要執行應用程式就會在偵錯工具中斷最多四次。
使用平行堆疊視窗:執行緒檢視
按一下 [偵錯] 功能表上的 [開始偵錯]。 等待叫用第一個中斷點。
檢視單一執行緒的呼叫堆疊
在 [偵錯] 功能表上,指向 [視窗],然後按一下 [執行緒]。 將 [執行緒] 視窗停駐在 Visual Studio 底部。
在 [偵錯] 功能表中,指向 [視窗],然後按一下 [呼叫堆疊]。 將 [呼叫堆疊] 視窗停駐在 Visual Studio 底部。
按兩下 [執行緒] 視窗中的執行緒,使它成為目前執行緒。 目前執行緒具有黃色箭號。 當您變更目前執行緒時,它的呼叫堆疊會出現在 [呼叫堆疊] 視窗中。
檢查平行堆疊視窗
在 [偵錯] 功能表上,指向 [視窗],然後按一下 [平行堆疊]。 確定左上角的方塊中已選取 [執行緒]。
透過使用 [平行堆疊] 視窗,您可以在一個檢視中同時檢視多個呼叫堆疊。 下圖顯示 [呼叫堆疊] 視窗上方的 [平行堆疊] 視窗。
主執行緒的呼叫堆疊會出現在一個方塊中,而其他四個執行緒的呼叫堆疊會一起出現在另一個方塊中。 四個執行緒形成一組是因為它們的堆疊框架共用相同的方法內容,也就是說,它們位於相同的方法中:A、B 和 C。 若要檢視共用相同方塊之執行緒的執行緒 ID 和名稱,請將滑鼠游標停留於標題上 ([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 個執行緒],以查看執行緒的執行緒 ID。 您可以將滑鼠游標停留於堆疊框架上,以查看執行緒 ID 和其他框架詳細資料。 藍色醒目提示表示目前執行緒,黃色箭號表示目前執行緒的作用中堆疊框架。
布條圖示 (與藍色和紅色波浪狀線條重疊) 表示非目前執行緒的作用中堆疊框架。 在 [呼叫堆疊] 視窗中,按兩下 S.B 來切換框架。 [平行堆疊] 視窗使用綠色弧形箭號圖示來表示目前執行緒的目前堆疊框架。
在 [執行緒] 視窗中,切換執行緒並觀察 [平行堆疊] 視窗中的檢視已更新。
您可以使用 [平行堆疊] 視窗中的捷徑功能表,切換至另一個執行緒,或切換至另一個執行緒的另一個框架。 例如,以滑鼠右鍵按一下 S.J,指向 [切換至框架],然後按一下命令。
以滑鼠右鍵按一下 S.C,並指向 [切換至框架]。 其中一個命令有核取記號,表示目前執行緒的堆疊框架。 您可以切換至相同執行緒的該框架 (只有綠色箭號會移動),也可以切換至另一個執行緒 (藍色醒目提示也會移動)。 下圖顯示子功能表。
當方法內容只有與一個堆疊框架關聯時,方塊標題會顯示 [1 個執行緒],您只要按兩下就可以切換至該框架。 如果您按兩下的方法內容有 1 個以上關聯的框架,則會自動出現功能表。 隨著您將滑鼠游標停留於方法內容上,請注意右邊的黑色三角形。 按一下該三角形也會顯示捷徑功能表。
對於具有許多執行緒的大型應用程式,您可能會想要只專注於其中一部分執行緒。 [平行堆疊] 視窗可以只顯示加上旗標之執行緒的呼叫堆疊。 在工具列上,按一下清單方塊旁邊的 [僅顯示有旗標的項目] 按鈕。
接下來,在 [執行緒] 視窗中,對每一個執行緒逐一加上旗標,以查看 [平行堆疊] 視窗中如何顯示它們的呼叫堆疊。 若要將執行緒加上旗標,請使用捷徑功能表或執行緒的第一個儲存格。 再按一次 [僅顯示有旗標的項目] 工具列按鈕,以顯示所有執行緒。
繼續執行至第三個中斷點為止
若要在遇到第三個中斷點之前繼續執行,請在 [偵錯] 功能表上,按一下 [繼續]。
當多個執行緒在相同方法中但方法不在呼叫堆疊的開頭時,方法會出現在不同方塊中。 位於目前中斷點的例子有 S.L,其中有三個執行緒,且分別出現在三個方塊中。 按兩下 S.L。
請注意,S.L 在其他兩個方塊中是粗體,所以您可以看到它出現在其他地方。 如果您要查看有哪些框架呼叫 S.L 和它呼叫哪些框架,請按一下工具列的 [切換方法檢視] 按鈕。 下圖顯示 [平行堆疊] 視窗的方法檢視。
請注意圖表如何隨選取的方法而轉移,以及它在檢視中間如何放在自己的方塊中。 被呼叫端和呼叫端出現在上方和下方。 再按一次 [切換方法檢視] 按鈕以結束這個模式。
[平行堆疊] 視窗的捷徑功能表還有下列其他項目。
[十六進位顯示] 在十進位和十六進位之間切換工具提示中的數字。
[符號載入資訊] 和 [符號設定] 會開啟各自的對話方塊。
[移至原始程式碼] 和 [移至反組譯碼] 會在編輯器中巡覽至選取的方法。
[顯示外部程式碼] 會顯示所有框架,即使不在使用者程式碼中也一樣。 請試著使用它來查看圖表如何展開來容納其他框架 (這些框架可能會因為您沒有它們的符號而呈現暗灰色)。
當您具有大型圖表並逐步執行至下一個中斷點時,您可能會想要讓檢視自動捲動至目前執行緒的作用中堆疊框架,也就是最先叫用中斷點的執行緒。 在 [平行堆疊] 視窗中,確定工具列上的 [自動捲動到目前堆疊框架] 按鈕已啟用。
繼續之前,在 [平行堆疊] 視窗中一直捲動到最左邊和最下方。
繼續執行至第四個中斷點為止
若要繼續執行至叫用第四個中斷點為止,請按一下 [偵錯] 功能表的 [繼續]。
請注意檢視如何自動捲動至定位。 在 [執行緒] 視窗中切換執行緒,或在 [呼叫堆疊] 視窗中切換堆疊框架,並注意檢視如何總是自動捲動至正確的框架。 關閉 [自動捲動到目前工具框架] 選項並檢視差異。
[概觀] 也有助於在 [平行堆疊] 視窗中顯示大型圖表。 您可以在視窗右下角按一下捲軸之間的按鈕,以查看 [概觀],如下圖所示。
您可以移動矩形以快速將圖表到處移動。
另一種往任何方向移動圖表的方式是按一下圖表的空白區域,並拖曳至您要的位置。
若要放大和縮小圖表,請在移動滑鼠滾輪時按住 CTRL。 或者,按一下工具列的 [縮放] 按鈕,然後使用 [縮放] 工具。
您也可以按一下 [工具] 功能表,再按一下 [選項],然後選取或清除 [偵錯] 節點下的選項,以使用由上而下的方向來檢視堆疊,而非由下而上。
繼續之前,按一下 [偵錯] 功能表上的 [停止偵錯] 以結束執行。
使用平行工作視窗和平行堆疊視窗的工作檢視
繼續之前,我們建議您完成先前的程序。
重新啟動應用程式直到叫用第一個中斷點為止
按一下 [偵錯] 功能表上的 [開始偵錯],並等待叫用第一個中斷點。
在 [偵錯] 功能表上,指向 [視窗],然後按一下 [執行緒]。 將 [執行緒] 視窗停駐在 Visual Studio 底部。
在 [偵錯] 功能表中,指向 [視窗],然後按一下 [呼叫堆疊]。 將 [呼叫堆疊] 視窗停駐在 Visual Studio 底部。
按兩下 [執行緒] 視窗中的執行緒,使它成為目前執行緒。 目前執行緒具有黃色箭號。 當您變更目前執行緒時,其他視窗會隨之更新。 我們接下來檢查工作。
在 [偵錯] 功能表上,指向 [視窗],然後按一下 [平行工作]。 下圖顯示 [平行工作] 視窗。
對於每一個執行中的工作,您可以讀取其 ID (由名稱相同的屬性傳回)、執行這個工作之執行緒的 ID 和名稱,以及它的位置 (將滑鼠游標停留於工作上會顯示包含整個呼叫堆疊的工具提示)。 另外,在 [工作] 資料行下,您可以查看傳入工作中的方法,也就是起點。
您可以排序任何資料行。 請注意表示排序資料行和方向的排序圖像。 您也可以將資料行向左或向右拖曳,以重新排列資料行。
黃色箭號表示目前工作。 您可以按兩下工作或使用捷徑功能表來切換工作。 當您切換工作時,基礎執行緒會變成目前執行緒,而其他視窗也會隨之更新。
當您手動在兩個工作之間切換時,黃色箭號會移動,但白色箭號仍然會顯示造成偵錯工具中斷的工作。
繼續執行至第二個中斷點為止
若要繼續執行至叫用第二個中斷點為止,請按一下 [偵錯] 功能表的 [繼續]。
先前 [狀態] 資料行將所有工作顯示為 [執行中],但現在有兩項工作是 [等待中]。 工作可能會因為許多不同的原因而受阻。 在 [狀態] 資料行中,將滑鼠游標停留於等待中工作上,以了解受阻的原因。 例如,在下圖中,工作 3 正在等待工作 4。
工作 4 又在等待指派給工作 2 的執行緒所擁有的監視器。
您可以按一下 [平行工作] 視窗的第一個資料行中的旗標,將工作加上旗標。
您可以使用旗標,在相同偵錯工作階段中的不同中斷點之間追蹤工作,或篩選在 [平行堆疊] 視窗中出現呼叫堆疊的工作。
您先前在使用 [平行堆疊] 視窗時,已檢視應用程式執行緒。 再次檢視 [平行堆疊] 視窗,但這次檢視應用程式工作。 作法是在左上方的方塊中選取 [工作]。 下圖顯示 [工作檢視]。
目前未執行工作的執行緒不會出現在 [平行堆疊] 視窗的 [工作檢視] 中。 另外,對於在執行工作的執行緒,某些與工作無關的堆疊框架則會從堆疊的上方和下方被過濾掉。
再次檢視 [平行工作] 視窗。 以滑鼠右鍵按一下任何資料行標題,以查看資料行的捷徑功能表。
您可以使用捷徑功能表來加入或移除資料行。 例如,AppDomain 資料行未選取,所以不會出現在清單中。 按一下 [父代]。 這四項工作在 [父代] 資料行中都沒有顯示值。
繼續執行至第三個中斷點為止
若要在遇到第三個中斷點之前繼續執行,請在 [偵錯] 功能表上,按一下 [繼續]。
新工作 (工作 5) 現在正在執行,而工作 4 現在正在等待。 您可以在 [狀態] 視窗中將滑鼠游標停留於等待中工作上,以查看原因。 在 [父代] 資料行中,請注意工作 4 是工作 5 的父代。
若要更明確顯示父子式關聯性,請以滑鼠右鍵按一下 [父代] 資料行標題,然後按一下 [父子式檢視]。 您應該會看到下圖。
請注意工作 4 和工作 5 在相同執行緒上執行。 這項資訊不會出現在 [執行緒] 視窗中,在這裡看到這項資訊是 [平行工作] 視窗的另一項優點。 若要確認這一點,請檢視 [平行堆疊] 視窗。 確定您檢視的是 [工作]。 在 [平行工作] 視窗中按兩下工作 4 和工作 5,找出它們。 這樣做時,[平行堆疊] 視窗中的藍色醒目提示會隨之更新。 您也可以瀏覽 [平行堆疊] 視窗上的工具提示來尋找工作 4 和 5。
在 [平行堆疊] 視窗中,以滑鼠右鍵按一下 S.P,然後按一下 [移至執行緒]。 視窗會切換至 [執行緒檢視],且檢視中會有對應的框架。 您可以在相同執行緒上同時查看這兩項工作。
相較於 [執行緒] 視窗,這是 [平行堆疊] 視窗的 [工作檢視] 的另一項優點。
繼續執行至第四個中斷點為止
若要在遇到第三個中斷點之前繼續執行,請在 [偵錯] 功能表上,按一下 [繼續]。 按一下 [ID] 資料行標題,依 ID 排序。 您應該會看到下圖。
因為工作 5 已完成,所以不會再出現。 如果您的電腦上不是這樣,也沒有顯示死結,請按 F11 逐步執行一次。
工作 3 和工作 4 現在正在互相等待,已形成死結。 工作 2 還有 5 個新的子工作已經進入排程準備執行。 排程工作是指已在程式碼中啟動但尚未執行的工作。 因此,其 [位置] 和 [執行緒指派] 資料行都是空的。
再次檢視 [平行堆疊] 視窗。 每一個方塊的標題都有工具提示會顯示執行緒 ID 和名稱。 切換至 [平行堆疊] 視窗中的 [工作檢視]。 將滑鼠游標停留於標題上,以查看工作 ID 和名稱,以及工作的狀態,如下圖所示。
您可以依資料行將工作分組。 在 [平行工作] 視窗中,以滑鼠右鍵按一下 [狀態] 資料行標題,然後按一下 [依狀態群組]。 下圖顯示依狀態分組的 [平行工作] 視窗。
您也可以依其他任何資料行進行分組。 將工作分組可讓您專注於一部分工作。 每一個可摺疊的群組都有一些組成該群組的項目。 您也可以按一下 [摺疊] 按鈕右邊的 [加上旗標] 按鈕,快速將群組中的所有項目加上旗標。
[平行工作] 視窗中最後一項要說明的功能,就是您以滑鼠右鍵按一下工作時所顯示的捷徑功能表。
視工作的狀態而定,捷徑功能表會顯示不同的命令。 命令可能包括 [複製]、[全選]、[十六進位顯示]、[切換至工作]、[凍結指派的執行緒]、[凍結這個執行緒以外的所有執行緒]、[解除凍結指派的執行緒] 和 [加上旗標]。
您可以凍結一項或多項工作的基礎執行緒,也可以凍結指派的執行緒除外的所有執行緒。 凍結的執行緒在 [平行工作] 視窗中以藍色「暫停」(Pause) 圖示表示,就像在 [執行緒] 視窗中一樣。
摘要
本逐步解說示範 [平行工作] 和 [平行堆疊] 偵錯工具視窗。 請在使用多執行緒程式碼的實際專案上使用這些視窗。 您可以檢查以 C++、C# 或 Visual Basic 撰寫的平行程式碼。