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