チュートリアル: Visual Studio での並列アプリケーションのデバッグ (C#、Visual Basic、C++)
このチュートリアルでは、 [並列タスク] ウィンドウと [並列スタック] ウィンドウを使用して並行アプリケーションをデバッグする方法について説明します。 これらのウィンドウは、タスク並列ライブラリ (TPL) または同時実行ランタイムを使用するコードの実行時の動作を理解し、検証するのに役立ちます。 このチュートリアルには、ブレークポイントが組み込まれたサンプル コードが用意されています。 ブレークポイントでコードの実行が中断されたら、 [並列タスク] ウィンドウと [並列スタック] ウィンドウを使用してコードを調べます。
このチュートリアルでは、次のタスクについて説明します。
すべてのスレッドの呼び出し履歴を 1 つのビューに表示する方法
アプリケーションで作成された
System.Threading.Tasks.Task
インスタンスの一覧を表示する方法スレッドではなくタスクの実際の呼び出し履歴を表示する方法
[並列タスク] ウィンドウや [並列スタック] ウィンドウからコードに移動する方法
グループ化やズームなどの機能を使用してウィンドウの表示を調整する方法
前提条件
このチュートリアルでは、[マイ コードのみ] が有効になっていることを前提としています (新しいバージョンの Visual Studio では既定で有効になっています)。 [ツール] メニューの [オプション] を選択し、[デバッグ] ノードを展開して [全般] を選択し、['マイ コードのみ' 設定を有効にする] を選択します。 この機能が設定されていなくてもこのチュートリアルを使用できますが、図と異なる結果になる可能性があります。
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(); }
コード ファイルを更新したら、変更を保存してソリューションをビルドします。
[ファイル] メニューの [すべてを保存] をクリックします。
[ビルド] メニューで、[ソリューションのリビルド] を選択します。
(C++ サンプル内の DebugBreak
に) Debugger.Break
に対する 4 つの呼び出しがあることに注意してください。 したがって、ブレークポイントを挿入する必要はありません。 アプリケーションを実行するだけで、デバッガーで最大 4 回中断します。
[並列スタック] ウィンドウの使用: スレッド ビュー
開始するには、[デバッグ] メニューの [デバッグの開始] を選択します。 最初のブレークポイントがヒットするまで待ちます。
1 つのスレッドの呼び出し履歴を表示する
[デバッグ] メニューの [ウィンドウ] をポイントし、[スレッド] を選択します。 [スレッド] ウィンドウを Visual Studio の下部にドッキングします。
[デバッグ] メニューの [ウィンドウ] をポイントし、[呼び出し履歴] を選択します。 [呼び出し履歴] ウィンドウを Visual Studio の下部にドッキングします。
[スレッド] ウィンドウのスレッドをダブルクリックして、そのスレッドを現在のスレッドにします。 現在のスレッドには黄色の矢印が表示されます。 現在のスレッドを変更すると、そのスレッドの呼び出し履歴が [呼び出し履歴] ウィンドウに表示されます。
[並列スタック] ウィンドウを調べる
[デバッグ] メニューの [ウィンドウ] をポイントし、[並列スタック] を選択します。 左上隅のボックスで [スレッド] が選択されていることを確認します。
[並列スタック] ウィンドウを使用すると、複数の呼び出し履歴を 1 つのビューに同時に表示できます。 次の図では、 [呼び出し履歴] ウィンドウの上に [並列スタック] ウィンドウが表示されています。
一方のボックスにはメイン スレッドの呼び出し履歴が表示され、もう一方のボックスには他の 4 つのスレッドの呼び出し履歴がグループ化されて表示されています。 4 つのスレッドがグループ化されているのは、これらのスレッドのスタック フレームが同じメソッド コンテキストを共有している (A
、B
、C
という同じメソッドにある) からです。 同じボックスを共有しているスレッドのスレッド ID と名前を表示するには、ヘッダー ("[#] 個のスレッド") が表示されているボックスをポイントします。 現在のスレッドが太字で表示されます。
黄色の矢印は、現在のスレッドのアクティブなスタック フレームを示します。
表示するスタック フレームの詳細情報 ( [モジュール名] 、 [パラメーターの型] 、 [パラメーター名] 、 [パラメーター値] 、 [行番号] 、および [バイト オフセット] ) を設定するには、 [呼び出し履歴] ウィンドウを右クリックします。
ボックスを囲む青の強調表示は、そのボックスに現在のスレッドが含まれていることを示します。 現在のスレッドは、ツールヒントで太字のスタック フレームによっても示されます。 [スレッド] ウィンドウでメイン スレッドをダブルクリックすると、それに応じて [並列スタック] ウィンドウの強調表示矢印が移動します。
2 つ目のブレークポイントまで実行を再開する
2 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] を選択します。 次の図は、2 つ目のブレークポイントのスレッド ツリーを示しています。
1 つ目のブレークポイントでは、4 つのスレッドのすべてが、S.A メソッド、S.B メソッド、S.C メソッドの順に進んでいました。 その情報もまだ [並列スタック] ウィンドウに表示されていますが、4 つのスレッドはさらに先に進んでいます。 そのうちの 1 つは、S.D、S.E の順に進んでいます。また別のスレッドは、S.F、S.G、S.H の順に進んでいます。残りの 2 つは、S.I、S.J の順に進み、そこから一方は S.K に進み、もう一方は非ユーザー外部コードに進んでいます。
スタック フレームをポイントすると、スレッド ID とフレームのその他の詳細が表示されます。 青の強調表示は現在のスレッドを示し、黄色の矢印は現在のスレッドのアクティブなスタック フレームを示します。
[1 個のスレッド] 、 [2 個のスレッド] などのボックス ヘッダーをポイントすると、スレッドのスレッド ID が表示されます。 スタック フレームをポイントすると、スレッド ID とフレームのその他の詳細が表示されます。 青の強調表示は現在のスレッドを示し、黄色の矢印は現在のスレッドのアクティブなスタック フレームを示します。
より糸のアイコン (交差している線) は、現在のスレッドではないスレッドのアクティブなスタック フレームを示します。 [呼び出し履歴] ウィンドウで S.B をダブルクリックしてフレームを切り替えます。 [並列スタック] ウィンドウで、現在のスレッドの現在のスタック フレームが曲線矢印のアイコンによって示されます。
注意
[並列スタック] ウィンドウのすべてのアイコンの詳細については、「[並列スタック] ウィンドウの使用」を参照してください。
[スレッド] ウィンドウでスレッドを切り替えて、 [並列スタック] ウィンドウのビューが更新されることを確認します。
別のスレッドに切り替えたり、別のスレッドの別のフレームに切り替えたりするには、 [並列スタック] ウィンドウのショートカット メニューを使用することもできます。 たとえば、S.J を右クリックし、[フレームに切り替え] をポイントして、いずれかのコマンドを選択します。
S.C を右クリックし、 [フレームに切り替え] をポイントします。 チェック マークが付いているコマンドがあります。これは、現在のスレッドのスタック フレームを示します。 その同じスレッドのフレームに切り替えることも (曲線矢印のみが移動します)、他のスレッドに切り替えることもできます (青の強調表示も移動します)。 次の図は、このサブメニューを示しています。
メソッド コンテキストに関連付けられているスタック フレームが 1 つしかない場合は、ボックス ヘッダーに [1 個のスレッド] と表示されます。この場合は、メソッド コンテキストをダブルクリックするだけでそのフレームに切り替えることができます。 複数のフレームが関連付けられているメソッド コンテキストをダブルクリックすると、自動的にメニューがポップアップ表示されます。 メソッド コンテキストをポイントすると右側に表示される黒い三角形を クリックして、このショートカット メニューを表示することもできます。
多数のスレッドを持つ大規模なアプリケーションでは、一部のスレッドのみに焦点を絞ることもできます。 [並列スタック] ウィンドウでは、フラグが設定されたスレッドの呼び出し履歴のみを表示することができます。 スレッドにフラグを設定するには、ショートカット メニューを使用するか、スレッドの最初のセルを使用します。
ツール バーで、リスト ボックスの横にある [フラグが設定されたもののみを表示] ボタンを選択します。
これで、フラグが設定されたスレッドのみが [並列スタック] ウィンドウに表示されるようになります。
3 つ目のブレークポイントまで実行を再開する
3 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] を選択します。
同じメソッドに複数のスレッドがあるが、そのメソッドが呼び出し履歴の先頭にない場合、そのメソッドは複数のボックスに表示されます。 たとえば、現在のブレークポイントの S.L がこれに当たります。このメソッドは 3 つのスレッドを持ち、3 つのボックスに表示されています。 S.L をダブルクリックします。
他の 2 つのボックスで S.L が太字になっていることに注目してください。これにより、このメソッドが他にどこにあるのかがわかります。 S.L がどのフレームから呼び出され、どのフレームを呼び出すのかを確認するには、ツール バーの [メソッド ビューの切り替え] ボタンを選択します。 次の図では、 [並列スタック] ウィンドウのメソッド ビューが示されています。
選択したメソッドを中心とする図が表示されます。選択したメソッドが固有のボックスに入れられて、ビューの中心に配置されます。 その上下には、呼び出し先と呼び出し元がそれぞれ表示されます。 もう一度 [メソッド ビューの切り替え] ボタンを選択して、このモードを終了します。
[並列スタック] ウィンドウのショートカット メニューには、次のような項目も含まれています。
[16 進数で表示] : ツールヒントの数値を 10 進数または 16 進数に切り替えます。
[シンボルの設定] : それぞれのダイアログ ボックスを開きます。
[ソースのスレッドを表示] : ソース コードでのスレッド マーカーの表示を切り替えます。スレッド マーカーは、ソース コードでのスレッドの位置を示します。
[外部コードの表示]: ユーザー コード以外のものも含めてすべてのフレームを表示します。 図にその他のフレームが表示されることを確認してください (シンボルがないために選択不可になっている可能性があります)。
[並列スタック] ウィンドウで、ツール バーの [現在のスタック フレームに自動スクロール] ボタンがオンになっていることを確認します。
大きな図がある場合は、次のブレークポイントにステップするときに、現在のスレッド (最初にブレークポイントにヒットしたスレッド) のアクティブなスタック フレームまでビューが自動的にスクロールするようにすることができます。
次に進む前に、 [並列スタック] ウィンドウを一番左の一番下までスクロールしておきます。
4 つ目のブレークポイントまで実行を再開する
4 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] を選択します。
ビューが適切な位置まで自動的にスクロールすることに注目してください。 [スレッド] ウィンドウでスレッドを切り替えたり、 [呼び出し履歴] ウィンドウでスタック フレームを切り替えたりして、ビューが常に適切なフレームまで自動的にスクロールすることを確認します。 さらに、 [現在のツール フレームに自動スクロール] オプションをオフにして違いを確認します。
[並列スタック] ウィンドウに大きな図がある場合は、バード アイ ビューも便利です。 既定では、バード アイ ビューがオンになっています。 ただし、次の図に示すように、ウィンドウの右下のスクロール バーの間にあるボタンをクリックすることで、切り替えることができます。
バード アイ ビューでは、四角形の枠を移動することにより、図のさまざまな場所をすばやく表示できます。
図を任意の方向に移動するには、図の空白の領域を選択して目的の方向にドラッグすることもできます。
図を拡大/縮小するには、Ctrl キーを押しながらマウス ホイールを動かします。 または、ツール バーの [ズーム] ボタンを選択してズーム ツールを使用します。
スタックを下から上の方向ではなく上から下の方向で表示することもできます。 [ツール] メニューの [オプション] をクリックし、 [デバッグ] ノードのオプションをオンまたはオフにします。
次に進む前に、[デバッグ] メニューの [デバッグの停止] を選択して実行を終了します。
[並列タスク] ウィンドウと、[並列スタック] ウィンドウのタスク ビューを使用する
先に進む前に前の手順を完了することをお勧めします。
1 つ目のブレークポイントにヒットするまでアプリケーションの実行を再開する
[デバッグ] メニューの [デバッグ開始] を選択し、1 つ目のブレークポイントにヒットするまで待ちます。
[デバッグ] メニューの [ウィンドウ] をポイントし、[スレッド] を選択します。 [スレッド] ウィンドウを Visual Studio の下部にドッキングします。
[デバッグ] メニューの [ウィンドウ] をポイントし、[呼び出し履歴] をクリックします。 [呼び出し履歴] ウィンドウを Visual Studio の下部にドッキングします。
[スレッド] ウィンドウのスレッドをダブルクリックして、そのスレッドを現在のスレッドにします。 現在のスレッドには黄色の矢印が表示されます。 現在のスレッドを変更すると、他のウィンドウが更新されます。 次に、タスクを調べます。
[デバッグ] メニューの [ウィンドウ] をポイントし、[タスク] を選択します。 次の図では、 [タスク] ウィンドウを示します。
実行中の各タスクについて、タスクの ID (同名のプロパティから返されます)、そのタスクを実行しているスレッドの ID と名前、およびタスクの場所 (ここをポイントすると、ツールヒントに呼び出し履歴全体が表示されます) が表示されます。 また、 [タスク] 列には、そのタスクに渡されたメソッド (開始点) が表示されます。
この一覧は、任意の列で並べ替えることができます。 並べ替えグリフにより、並べ替えの列と方向が示されます。 列を左右にドラッグして列の順序を変更することもできます。
黄色の矢印は現在のタスクを示します。 タスクを切り替えるには、タスクをダブルクリックするか、ショートカット メニューを使用します。 タスクを切り替えると、基になるスレッドが現在のスレッドになり、他のウィンドウが更新されます。
あるタスクから別のタスクに手動で切り替えると、枠線の矢印は、現在のタスク以外のタスクの現在のデバッガー コンテキストを示します。
タスクを手動で切り替えると黄色の矢印が移動しますが、デバッガーが中断されたタスクは引き続き白の矢印によって示されます。
2 つ目のブレークポイントまで実行を再開する
2 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] を選択します。
先ほどは、 [状態] 列ですべてのタスクがアクティブになっていましたが、現在は 2 つのタスクがブロックになっています。 タスクがブロックされるのにはさまざまな理由があります。 [状態] 列で待機中のタスクをポイントすると、タスクがブロックされている理由を確認できます。 たとえば、次の図では、タスク 11 はタスク 12 を待機しています。
先ほどは、 [状態] 列ですべてのタスクがアクティブになっていましたが、現在は 2 つのタスクがブロックになっています。 タスクがブロックされるのにはさまざまな理由があります。 [状態] 列で待機中のタスクをポイントすると、タスクがブロックされている理由を確認できます。 たとえば、次の図では、タスク 4 はタスク 5 を待機しています。
一方、タスク 4 は、タスク 2 に割り当てられているスレッドが所有するモニターを待機しています。 (タスク 2 のスレッド割り当て値を表示するには、ヘッダー行を右クリックして、 [列]>[スレッドの割り当て] を選択します)。
[タスク] ウィンドウの最初の列のフラグをクリックすると、タスクにフラグを設定できます。
フラグを設定することにより、同じデバッグ セッションの異なるブレークポイントの間でタスクを追跡したり、 [並列スタック] ウィンドウに呼び出し履歴を表示するタスクをフィルター選択したりすることができます。
先ほど [並列スタック] ウィンドウを使用したときにはアプリケーションのスレッドを表示しましたが、 今度は [並列スタック] ウィンドウでアプリケーションのタスクを表示します。 そのためには、左上のボックスで [タスク] を選択します。 次の図は、タスク ビューを示しています。
[並列スタック] ウィンドウのタスク ビューには、現在タスクを実行していないスレッドは表示されません。 タスクを実行するスレッドについても、タスクに関連しないスタック フレームはスタックの上下からフィルターで除外されます。
[タスク] ウィンドウを再び表示します。 任意の列ヘッダーを右クリックして、その列のショートカット メニューを表示します。
ショートカット メニューを使用して、列を追加したり削除したりすることができます。 たとえば、[AppDomain] 列は選択されていないため、リストに表示されていません。 [親] を選択します。 [親] 列が表示されますが、4 つのタスクのいずれにもこの列の値はありません。
3 つ目のブレークポイントまで実行を再開する
3 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] を選択します。
この実行例では、タスク 11 とタスク 12 が同じスレッドで実行されていることに注意してください (非表示になっている場合は、[スレッドの割り当て] 列を表示します)。 この情報は、[スレッド] ウィンドウには表示されません。この情報を表示できるのも、[タスク] ウィンドウの利点の 1 つです。 この情報を確認するために、 [並列スタック] ウィンドウを表示します。 ビューが [タスク] になっていることを確認します。 [並列スタック] ウィンドウでツールヒントを調べてタスク 11 とタスク 12 を見つけることもできます。
新しいタスク (タスク 5) が実行中になり、タスク 4 は待機中になっています。 [状態] 列でこの待機中のタスクをポイントすると、理由が表示されます。 [親] 列を見ると、タスク 4 はタスク 5 の親であることがわかります。
親子関係をわかりやすく表示するには、列ヘッダー行を右クリックし、[親子ビュー] を選択します。 次の図のような画面が表示されます。
タスク 4 とタスク 5 が同じスレッドで実行されていることに注意してください (非表示になっている場合は、[スレッドの割り当て] 列を表示します)。 この情報は、[スレッド] ウィンドウには表示されません。この情報を表示できるのも、[タスク] ウィンドウの利点の 1 つです。 この情報を確認するために、 [並列スタック] ウィンドウを表示します。 ビューが [タスク] になっていることを確認します。 タスク 4 とタスク 5 を見つけるには、 [タスク] ウィンドウでそれらをダブルクリックします。 これにより、 [並列スタック] ウィンドウの青の強調表示が更新されます。 [並列スタック] ウィンドウでツールヒントを調べてタスク 4 とタスク 5 を見つけることもできます。
[並列スタック] ウィンドウで、S.P を右クリックし、[スレッドに移動] を選択します。 ウィンドウがスレッド ビューに切り替わり、対応するフレームが表示されます。 両方のタスクが同じスレッドにあることがわかります。
これもまた、 [スレッド] ウィンドウにはない、 [並列スタック] ウィンドウのタスク ビューならではの利点です。
4 つ目のブレークポイントまで実行を再開する
3 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] を選択します。 [ID] 列の列ヘッダーを選択して ID で並べ替えます。 次の図のような画面が表示されます。
タスク 10 とタスク 11 は互いを待機しており、ブロックされています。 また、いくつかの新しいタスクがスケジュールされています。 スケジュールされたタスクとは、コードで開始されているがまだ実行されていないタスクです。 したがって、[場所] 列と [スレッドの割り当て] 列に既定のメッセージが表示されるか、空になります。
タスク 5 は完了したため、もう表示されません。 お使いのコンピューターでそうならず、デッドロックが表示されていない場合は、F11 キーを押して 1 回ステップ実行してください。
タスク 3 とタスク 4 は互いを待機しており、ブロックの状態になっています。 また、5 つの新しいタスクがあります。これらはタスク 2 の子で、現在スケジュールされているタスクです。 スケジュールされたタスクとは、コードで開始されているがまだ実行されていないタスクです。 したがって、 [場所] 列と [スレッドの割り当て] 列が空になっています。
再び [並列スタック] ウィンドウを表示します。 各ボックスのヘッダーをポイントすると、ツールヒントにスレッドの ID と名前が表示されます。 [並列スタック] ウィンドウのタスク ビューに切り替えます。 次の図のようにヘッダーをポイントして、タスクの ID、名前、および状態を確認します。
タスクを列でグループ化することもできます。 [タスク] ウィンドウで、[状態] 列の列ヘッダーを右クリックし、[状態でグループ化] を選択します。 次の図は、状態でグループ化された [タスク] ウィンドウを示しています。
ほかにも任意の列でグループ化できます。 タスクをグループ化すると、一部のタスクに焦点を絞ることができます。 各グループは折りたたみ可能で、それぞれにグループ化されている項目の数が表示されます。
最後に、 [タスク] ウィンドウでタスクを右クリックすると表示されるショートカット メニューを確認します。
このショートカット メニューに表示されるコマンドは、タスクの状態によって異なります。 たとえば、[コピー]、[すべて選択]、[16 進数で表示]、[タスクに切り替え]、[割り当てられたスレッドを凍結]、[これ以外のすべてのスレッドを凍結]、[割り当てられたスレッドの凍結を解除]、[フラグ設定] などのコマンドが表示されます。
1 つまたは複数のタスクの基になるスレッドを凍結したり、割り当てられたスレッド以外のすべてのスレッドを凍結したりすることができます。 凍結されたスレッドは、 [タスク] ウィンドウでも、 [スレッド] ウィンドウと同じように青い "一時停止" アイコンで表されます。
まとめ
このチュートリアルでは、デバッガーの [並列タスク] ウィンドウと [並列スタック] ウィンドウについて説明しました。 マルチスレッド コードを使用する実際のプロジェクトでこれらのウィンドウを使用してみてください。 C++、C#、または Visual Basic で記述された並列コードを調べることができます。