本逐步解說示範如何使用 平行工作 和 平行堆疊 視窗來偵錯平行應用程式。 這些視窗可協助您了解及驗證使用工作平行連結庫或並行運行時間的程式代碼運行時間行為。 本分步教學提供內建斷點的程式碼範例。 在程式代碼中斷之後,本演練會示範如何使用 平行工作 和 平行堆棧 視窗來檢查代碼。
本逐步解說會教你如何完成這些任務:
如何在一個檢視中檢視所有線程的呼叫堆疊。
如何檢視應用程式中建立的
System.Threading.Tasks.Task
實例清單。如何檢視工作的實際呼叫堆疊,而不是線程。
如何從 [平行工作] 和 [平行堆棧] 視窗導航至程式碼。
視窗如何透過群組、縮放和其他相關功能來處理縮放比例。
先決條件
本逐步解說假設已啟用 Just My Code (預設會在較新版本的 Visual Studio 中啟用)。 在 [工具] 功能表上,選取 [選項],展開 [偵錯] 節點,選取 [一般],然後選取 [啟用 Just My Code (僅限受管理)]。 如果您未設定此功能,您仍然可以使用此逐步解說,但結果可能與圖例不同。
C# 範例
如果您使用 C# 範例,本逐步解說也會假設外部程式代碼已隱藏。 若要切換是否顯示外部程式碼,請以滑鼠右鍵按兩下 [呼叫堆疊] 視窗的 [名稱] 資料表標頭,然後選取或清除 [顯示外部程式代碼]。 如果您未設定此功能,您仍然可以使用此逐步解說,但結果可能與圖例不同。
C++範例
如果您使用C++範例,您可以忽略本文中外部程式代碼的參考。 外部程式代碼僅適用於 C# 範例。
插圖
本文中的圖例會記錄在執行 C# 範例的四核心計算機上。 雖然您可以使用其他組態來完成本逐步解說,但圖例可能會與計算機上顯示的內容不同。
建立範例專案
本逐步解說中的範例程式代碼適用於不會執行任何動作的應用程式。 練習的目的是要瞭解如何使用工具視窗來偵錯平行應用程式。
開啟 Visual Studio 並建立新的專案。
如果啟動視窗未開啟,請選擇 [ 檔案>開始視窗]。
在 [開始] 視窗中,選擇 [ 新增專案]。
在 [開始] 視窗中,選擇 [建立新專案]。
在 [ 建立新專案 ] 視窗中,於搜尋方塊中輸入或輸入 控制台 。 接下來,從 [語言] 列表中選擇 [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
(DebugBreak
在 C++ 範例中)。 因此,您不需要插入斷點。 只要執行應用程式,它就會使調試程式中斷最多四次。
使用平行堆疊視窗:線程檢視
若要開始,請在 [ 偵錯] 功能表上,選取 [ 開始偵錯]。 等到第一個斷點被觸發為止。
檢視單個線程的呼叫堆疊
在 [ 偵錯] 功能表上,指向 [Windows ],然後選取 [ 線程]。 將 [線程] 視窗停駐在 Visual Studio 底部。
在 [ 偵錯] 功能表上,指向 [Windows ],然後選取 [ 呼叫堆棧]。 將 呼叫堆棧 視窗停駐在 Visual Studio 的底部。
按兩下 [線程 ] 視窗中的線程,使其成為目前的線程。 目前的線程有黃色箭號。 當您變更目前線程時,其呼叫堆疊會顯示在 [呼叫堆棧 ] 視窗中。
檢查平行堆疊視窗
在 [ 偵錯] 功能表上,指向 [Windows ],然後選取 [ 平行堆棧]。 請確定已在左上角的方塊中選取討論串。
藉由使用 [平行堆棧 ] 視窗,您可以在一個檢視中同時檢視多個呼叫堆疊。 下圖顯示 [呼叫堆疊] 視窗上方的 [平行堆棧] 視窗。
Main 線程的呼叫堆疊會出現在一個方塊中,而其他四個線程的呼叫堆疊會分組在另一個方塊中。 四個線程會分組在一起,因為它們的堆疊框架共用相同的方法內容;也就是說,它們位於相同的方法: 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,另一個則繼續前往非使用者外部程式碼。
您可以將滑鼠停留在堆疊框架上,以查看線程標識碼和其他框架詳細數據。 藍色標示表示目前線程,而黃色箭號表示目前線程的活動堆疊框架。
您可以將滑鼠停留在方塊標頭上,例如 1 個線程 或 2 個線程,以查看線程的線程標識碼。 您可以將滑鼠停留在堆疊框架上,以查看線程標識碼和其他框架詳細數據。 藍色標示表示目前線程,而黃色箭號表示目前線程的活動堆疊框架。
非當前線程的活動堆疊框架由布線程圖示(交錯線條)表示。 在 [ 呼叫堆棧] 視窗中,按兩下 [S.B] 切換框架。 [平行堆棧] 視窗會使用弧形箭號圖示,指出目前線程的目前堆棧框架。
備註
如需平行堆疊視窗中所有圖示的描述,請參閱 使用平行堆疊視窗。
在 [ 線程] 視窗中,切換線程,並觀察 [平行堆棧 ] 視窗中的檢視已更新。
您可以使用 平行堆棧 視窗中的快捷選單切換到其他線程或其他線程的框架。 例如,以滑鼠右鍵按兩下 S.J,指向 [ 切換至框架],然後選取命令。
以滑鼠右鍵按兩下 [S.C],然後指向 [ 切換至框架]。 其中一個命令具有一個複選標記,指出目前線程的堆疊框架。 您可以切換至相同線程的框架(只有弧形箭號移動),也可以切換至另一個線程(藍色醒目提示也會移動)。 下圖顯示子功能表。
在 J 作為當前項目時,C 上的堆疊選單有兩個選項 PDB_Walkthrough_3
當方法內容只與一個堆疊框架相關聯時,方塊標頭會顯示 1 個線程 ,您可以按兩下來切換至該框架。 如果您按兩下某個方法上下文,而該上下文與之相關的框架超過一個,則功能表會自動跳出。 當您將滑鼠停留在方法內容上時,請注意右邊的黑色三角形。 按兩下該三角形也會顯示快捷方式功能表。
對於具有許多線程的大型應用程式,您可能只想將焦點放在線程子集上。 [平行堆疊] 視窗只能針對標示的線程顯示呼叫堆疊。 若要標記線程,請使用快捷方式功能表或線程的第一個儲存格。
在工具列上,選取清單框旁的 [ 僅顯示標幟] 按鈕。
現在,只有在平行堆疊視窗中顯示已標記的執行緒。
繼續執行,直到第三個斷點為止
若要繼續執行,直到達到第三個斷點為止,請在 [ 偵 錯] 功能表上,選取 [ 繼續]。
當多個線程位於相同的方法,但方法不在呼叫堆疊的開頭時,方法會出現在不同的方塊中。 目前斷點的範例是 S.L,其中有三個線程,並出現在三個方塊中。 按兩下 [S.L]。
螢幕快照。
請注意,S.L 在其他兩個方塊中以粗體顯示,以便您看到它在哪裡出現。 如果您想要查看哪些框架呼叫 S.L,以及 S.L 呼叫的框架,請選取工具列上的 切換方法檢視 按鈕。 下圖顯示 [ 平行堆棧 ] 視窗的方法檢視。
請注意,圖表如何圍繞選定的方法旋轉,並將該方法放置在檢視中間的框中。 通話者和撥號者分別出現在頂端和底部。 再次選取 [ 切換方法檢視 ] 按鈕以離開此模式。
[平行堆棧] 視窗的捷徑選單也有下列其他項目。
十六進位顯示 會在十進位和十六進位之間切換工具提示中的數位。
符號設定 會開啟個別對話框。
在 [來源中顯示線程 ] 會切換原始程式碼中線程標記的顯示,這會顯示原始碼中線程的位置。
顯示外部程式代碼 會顯示所有框架,即使它們不在使用者程式代碼中。 試試看以查看圖表展開以容納其他框架(可能會顯示為暗灰色,因為您缺少符號標記)。
在 [ 平行堆疊] 視窗中,確定工具列上的 [ 自動捲動至目前堆疊框架 ] 按鈕已開啟。
當您有大型圖表並逐步執行下一個斷點時,您可能會希望檢視自動捲動至目前執行緒的活動堆疊框架,也就是說,首個打到斷點的執行緒。
在繼續之前,請在 平行堆棧 視窗中,一路向左捲動,一路向下捲動。
繼續執行,直到第四個斷點為止
若要繼續執行,直到達到第四個斷點為止,請在 [ 偵 錯] 功能表上,選取 [ 繼續]。
請注意檢視視圖是如何自動捲動到指定位置的。 在 線程 視窗中切換線程,或在 呼叫堆疊 視窗中切換堆疊幀,並注意檢視一律會自動捲動到正確的幀。 關閉 [ 自動捲動至目前工具框架 ] 選項並檢視差異。
「鳥瞰檢視」 也能在 「平行堆疊」 視窗中協助查看大型圖表。 根據預設, [鳥眼檢視 ] 為開啟。 但是,您可以點擊視窗右下角捲動條之間的按鈕來切換它,如下圖所示。
在鳥眼檢視中,您可以移動矩形以快速瀏覽圖表。
以任何方向移動圖表的另一種方式是選取圖表的空白區域,並將它拖曳到您想要的位置。
若要放大和縮小圖表,請在移動滑鼠滾輪時按住 CTRL 鍵。 或者,選取工具列上的 [縮放] 按鈕,然後使用 [縮放] 工具。
您也可以按一下 [工具] 功能表,選擇 [選項],然後在 [偵錯] 節點下選取或清除選項,以從上到下檢視堆疊,而不是從下到上檢視堆疊。
繼續之前,請在 [ 偵錯] 功能表上,選取 [ 停止偵錯 ] 以結束執行。
使用 [平行工作] 視窗及 [平行堆疊] 視窗中的 [工作檢視]
建議您先完成先前的程式,再繼續。
重新啟動應用程式,直到達到第一個斷點為止:
在 [偵錯] 功能表上,選取 [開始偵錯],並等候程式執行至第一個斷點。
在 [ 偵錯] 功能表上,指向 [Windows ],然後選取 [ 線程]。 將 [線程] 視窗停駐在 Visual Studio 底部。
在 [ 偵錯] 功能表上,指向 [Windows ],然後選取 [ 呼叫堆棧]。 將 呼叫堆棧 視窗停駐於 Visual Studio 的底部。
按兩下 [線程 ] 視窗中的線程,使其成為目前的線程。 目前的線程具有黃色箭號。 當您變更目前的線程時,會更新其他視窗。 接下來,我們會檢視任務。
在偵錯功能表上,指向Windows,然後選取工作。 下圖顯示 [ 工作 ] 視窗。
[任務]
對於每個執行中的任務,您可以讀取其ID,此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],然後選取 [ 移至線程]。 視窗會切換至 [線程檢視],而對應的框架則處於檢視中。 您可以在相同的線程上看到這兩項工作。
相較於 [線程] 視窗,這是平行堆棧視窗中 [工作檢視] 的另一個優點。
繼續執行,直到第四個斷點為止
若要繼續執行,直到達到第三個斷點為止,請在 [ 偵 錯] 功能表上,選取 [ 繼續]。 選擇 ID 欄位標題以依照 ID 排序。 您應該會看到下圖。
工作 10 和工作 11 現在彼此等候,並遭到封鎖。 現在還有數個新工作已經安排好。 排程的工作是已在程式代碼中啟動但尚未執行的工作。 因此,其 [位置 ] 和 [ 線程指派 ] 資料行會顯示預設訊息或空白。
因為工作 5 已完成,所以不再顯示。 如果您的計算機上未出現此問題,且未顯示死鎖,請按 F11 逐步執行。
任務 3 和任務 4 現在相互等待並被阻礙。 另外還有5個新的任務是任務2的子任務,現已排程完成。 排程的工作是已在程式代碼中啟動但尚未執行的工作。 因此,其 [位置 ] 和 [ 線程指派 ] 數據行是空的。
再次檢視 [平行堆疊] 視窗。 每個方塊的標頭都有一個工具提示,顯示線程標識碼和名稱。 切換至 平行堆疊 視窗中的工作檢視。 將滑鼠停留在標頭上方以查看工作標識碼和名稱,以及工作的狀態,如下圖所示。
您可以依資料欄分組工作。 在 [ 工作] 視窗中,以滑鼠右鍵按兩下 [ 狀態 ] 資料行標頭,然後選取 [ 依狀態分組]。 下圖顯示依狀態分組的 [ 工作 ] 視窗。
[工作]
您也可以依任何其他欄分組。 藉由分組工作,您可以專注於工作的子集。 每個可折疊群組都有一個項目計數,表示屬於同一群組的項目數量。
在 [工作] 視窗中要檢查的最後一項功能是當您以滑鼠右鍵按一下工作時顯示的捷徑選單。
快捷方式功能表會根據工作的狀態顯示不同的命令。 這些命令可能包括 複製、全選、十六進位顯示、切換至任務、凍結已分配的執行緒、除本執行緒外凍結所有執行緒、解除凍結已分配的執行緒 和 標記。
您可以凍結任務的基礎線程,也可以凍結除指定線程以外的所有線程。 凍結的線程在 [工作] 視窗中會顯示為藍色暫停圖示,就像在 [線程] 視窗中一樣。
總結
本指南示範 平行工作 和 平行堆疊 的偵錯工具視窗。 在使用多線程程式代碼的實際專案上使用這些視窗。 您可以檢查以 C++、C# 或 Visual Basic 撰寫的平行程式代碼。