マルチスレッド アプリケーションのデバッグを始める (C#、Visual Basic、C++)
Visual Studio には、マルチスレッド アプリケーションのデバッグに役立つツールとユーザー インターフェイス要素がいくつか用意されています。 このチュートリアルでは、スレッド マーカー、 [並列スタック] ウィンドウ、 [並列ウォッチ] ウィンドウ、条件付きブレークポイントの使用方法と、ブレークポイントのフィルター方法について説明します。 このチュートリアルを完了すると、マルチスレッド アプリケーションをデバッグするための Visual Studio の機能を理解できます。
次の 2 つの記事では、他のマルチスレッド デバッグ ツールの使用に関する追加情報が提供されています。
[デバッグの場所] ツール バーと [スレッド] ウィンドウの使用方法については、チュートリアル: マルチスレッド アプリケーションのデバッグに関するページを参照してください。
Task (マネージド コード) と同時実行ランタイム (C++) を使用するサンプルについては、チュートリアル: 並行アプリケーションのデバッグに関するページを参照してください。 ほとんどのマルチスレッド アプリケーションの種類に適用される一般的なデバッグのヒントについては、その記事とこの記事の両方を参照してください。
最初の手順は、マルチスレッド アプリケーション プロジェクトを作成することです。
マルチスレッド アプリ プロジェクトを作成する
Visual Studio を起動し、新しいプロジェクトを作成します。
スタート ウィンドウが開いていない場合は、[ファイル]>[スタート ウィンドウ] を選択します。
スタート ウィンドウで、 [新しいプロジェクトの作成] を選択します。
[新しいプロジェクトの作成] ウィンドウで、検索ボックスに「コンソール」と入力またはタイプします。 次に、[言語] の一覧から [C#] 、 [C++] 、または [Visual Basic] を選択し、[プラットフォーム] の一覧から [Windows] を選択します。
言語およびプラットフォームのフィルターを適用してから、.NET または C++ 用の [コンソール アプリ] テンプレートを選択して、[次へ] を選択します。
注意
正しいテンプレートが表示されない場合は、 [ツール]>[ツールと機能を取得...] に移動して、Visual Studio インストーラーを開きます。 [.NET デスクトップ開発] ワークロードまたは [C++ によるデスクトップ開発] ワークロードを選択し、 [変更] を選択します。
[新しいプロジェクトの構成] ウィンドウの [プロジェクト名] ボックスに「MyThreadWalkthroughApp」と入力します。 次に、 [次へ] または [作成] を選択します (いずれかのオプションを使用できます)。
.NET Core または .NET 5+ プロジェクトの場合は、推奨されるターゲット フレームワークまたは .NET 8 を選択し、[作成] を選択します。
新しいコンソール プロジェクトが表示されます。 プロジェクトが作成されると、ソース ファイルが表示されます。 選択した言語に応じて、ソース ファイルの名前は Program.cs、MyThreadWalkthroughApp.cpp、Module1.vb などとなります。
ソース ファイルに表示されるコードを削除し、次の更新済みコードに置き換えます。 コード構成に適したスニペットを選択します。
using System; using System.Threading; public class ServerClass { static int count = 0; // The method that will be called when the thread is started. public void InstanceMethod() { Console.WriteLine( "ServerClass.InstanceMethod is running on another thread."); int data = count++; // Pause for a moment to provide a delay to make // threads more apparent. Thread.Sleep(3000); Console.WriteLine( "The instance method called by the worker thread has ended. " + data); } } public class Simple { public static void Main() { for (int i = 0; i < 10; i++) { CreateThreads(); } } public static void CreateThreads() { ServerClass serverObject = new ServerClass(); Thread InstanceCaller = new Thread(new ThreadStart(serverObject.InstanceMethod)); // Start the thread. InstanceCaller.Start(); Console.WriteLine("The Main() thread calls this after " + "starting the new InstanceCaller thread."); } }
[ファイル] メニューの [すべてを保存] をクリックします。
(Visual Basic のみ) ソリューション エクスプローラー (右ペイン) でプロジェクト ノードを右クリックし、 [プロパティ] を選択します。 [アプリケーション] タブで、 [スタートアップ オブジェクト] を [シンプル] に変更します。
マルチスレッド アプリをデバッグする
ソース コード エディターで、次のコード スニペットを探します。
Thread.Sleep
または C++ の場合はstd::this_thread::sleep_for
ステートメントの左側の余白を左クリックして、新しいブレークポイントを挿入します。余白の赤い円は、ブレークポイントがこの場所に設定されていることを示します。
[デバッグ] メニューの [デバッグの開始] (F5 キー) を選択します。
Visual Studio によってソリューションがビルドされ、デバッガーがアタッチされた状態でアプリの実行が開始されて、ブレークポイントでアプリが停止します。
ソース コード エディターで、ブレークポイントが含まれている行を探します。
スレッド マーカーを検出する
[デバッグ] ツール バーで、[ソースのスレッドを表示] ボタン を選択します。
F11 キーを 2 回押してデバッガーを進める。
ウィンドウ左端の余白に注目します。 この行では、2 本の撚糸に似た "スレッド マーカー" アイコン に注目してください。 スレッド マーカーは、スレッドが停止している位置を示します。
スレッド マーカーは、ブレークポイントによって部分的に隠されている場合があります。
スレッド マーカーの上にポインターを置きます。 データヒントには、停止されている各スレッドの名前とスレッド ID 番号が表示されます。 この場合、名前はおそらく
<noname>
になります。スレッド マーカーを選択すると、ショートカット メニューに使用可能なオプションが表示されます。
スレッドの場所を表示する
[並列スタック] ウィンドウでは、[スレッド] ビューと (タスク ベースのプログラミングの場合) [タスク] ビューを切り替えることができ、各スレッドの呼び出し履歴情報を表示できます。 このアプリでは、[スレッド] ビューを使用できます。
[デバッグ]>[Windows]>[並列スタック] を選択して、 [並列スタック] ウィンドウを開きます。 次のような内容が表示されます。 正確な情報は、各スレッドの現在の位置、ハードウェア、プログラミング言語によって異なります。
この例では、左から右に、マネージド コードに関する次の情報が表示されています。
- 現在のスレッド (黄色の矢印) が に
ServerClass.InstanceMethod
が入力されました。ServerClass.InstanceMethod
にカーソルを合わせると、スレッドのスレッド ID とスタック フレームを表示できます。 - スレッド 31724 は、スレッド 20272 が所有するロックを待機しています。
- [外部コード] でメイン スレッド (左側) が停止しました。[外部コードの表示] を選択すると詳細を確認できます。
この例では、左から右に、マネージド コードに関する次の情報が表示されています。
- メイン スレッド (左側) は
Thread.Start
で停止しています。ここでは、停止ポイントはスレッド マーカー アイコン で識別されます。 - 2 つのスレッドが
ServerClass.InstanceMethod
に入っています。1 つは現在のスレッド (黄色の矢印) で、もう一方のスレッドはThread.Sleep
で停止しています。 - 新しいスレッド (右側) も開始していますが、
ThreadHelper.ThreadStart
で停止されています。
- 現在のスレッド (黄色の矢印) が に
リスト ビューでスレッドを表示するには、[>Windows >スレッドのデバッグ] を選択します。
このビューでは、スレッド 20272 がメイン スレッドであり、現在は外部コード (特に System.Console.dll) に配置されていることを簡単に確認できます。
注意
[スレッド] ウィンドウの使用方法の詳細については、「チュートリアル: マルチスレッド アプリケーションをデバッグする」を参照してください。
[並列スタック] ウィンドウまたは [スレッド] ウィンドウでエントリを右クリックし、ショートカット メニューで使用可能なオプションを表示します。
これらの右クリック メニューからさまざまなアクションを実行できます。 このチュートリアルでは、並列ウォッチ ウィンドウ (次のセクション) でこれらの詳細について詳しく説明します。
変数にウォッチを設定する
[デバッグ]>[Windows]>[並列ウォッチ]>[並列ウォッチ 1] を選択して、 [並列ウォッチ] ウィンドウを開きます。
<Add Watch>
というテキストが表示されているセル (または 4 番目の列の空のヘッダー セル) を選択し、「data
」と入力します。各スレッドのデータ変数の値がウィンドウに表示されます。
<Add Watch>
というテキストが表示されているセル (または 5 番目の列の空のヘッダー セル) を選択し、「count
」と入力します。各スレッドの
count
変数の値がウィンドウに表示されます。 この情報がまだ表示されない場合は、F11 キーを数回押して、デバッガーでスレッドの実行を進めてください。ウィンドウ内のいずれかの行を右クリックし、使用可能なオプションを表示します。
スレッドに対するフラグの設定と設定解除を行う
スレッドにフラグを付けることで、重要なスレッドを追跡し、他のスレッドを無視することができます。
[並列ウォッチ] ウィンドウで、Shift キーを押しながら複数の行を選択します。
右クリックして、 [フラグ] を選択します。
選択したすべてのスレッドにフラグが設定されます。 これで、フィルター処理して、フラグが設定されたスレッドのみを表示できるようになります。
[並列ウォッチ] ウィンドウで、[フラグが設定されたスレッドのみを表示] ボタン を選択します。
フラグが設定されたスレッドのみが一覧に表示されます。
ヒント
いくつかのスレッドにフラグを設定したら、コード エディターでコード行を右クリックし、 [フラグが設定されたスレッドをカーソル行の前まで実行] を選択できます。 フラグが設定されたすべてのスレッドが到達するコードを選択してください。 Visual Studio により選択したコード行でスレッドが一時停止されるので、スレッドを凍結および凍結解除することにより、実行の順序を簡単に制御できます。
すべてのスレッドを表示モードに戻すには、 [フラグが設定されたスレッドのみを表示] ボタンをもう一度選択します。
スレッドのフラグを解除するには、 [並列ウォッチ] ウィンドウでフラグが設定されたスレッドを 1 つ以上右クリックして、 [フラグ解除] を選択します。
スレッドの実行を凍結および凍結解除する
ヒント
スレッドを凍結および凍結解除 (一時停止と再開) して、スレッドによる処理の実行順序を制御できます。 これは、デッドロックや競合状態など、同時実行の問題を解決するのに役立ちます。
[並列ウォッチ] ウィンドウですべての行を選択し、右クリックして、 [凍結] を選択します。
各行の 2 番目の列に、一時停止アイコンが表示されます。 一時停止アイコンは、スレッドが凍結されていることを示します。
1 行だけを選択し、他のすべての行の選択を解除します。
行を右クリックして、 [凍結解除] を選択します。
この行の一時停止アイコンが消え、スレッドが凍結されなくなったことが示されます。
コード エディターに切り替えて、F11 キーを押します。 凍結されていないスレッドだけが実行されます。
アプリで新しいスレッドがインスタンス化される場合があります。 新しいスレッドはすべて、フラグを設定されておらず、凍結されていません。
条件付きブレークポイントを使用して 1 つのスレッドを追跡する
デバッガーで 1 つのスレッドの実行を追跡すると、便利な場合があります。 これを行う 1 つの方法は、関心のないスレッドを凍結することです。 特定のバグを再現する場合など、シナリオによっては、他のスレッドを凍結せずに 1 つのスレッドを追跡することが必要になる場合があります。 他のスレッドを凍結せずにスレッドを追跡するには、関心のあるスレッド以外のコードに割り込まないようにする必要があります。 これを行うには、条件付きブレークポイントを設定します。
スレッド名やスレッド ID など、さまざまな条件でブレークポイントを設定できます。 スレッドごとに一意であることがわかっているデータで条件を設定すると、便利な場合があります。 このアプローチは、特定のスレッドよりも特定のデータ値に関心がある場合、デバッグ時に一般的に使用されます。
前に作成したブレークポイントを右クリックし、 [条件] を選択します。
[ブレークポイント設定] ウィンドウで、条件式に「
data == 5
」と入力します。ヒント
特定のスレッドの方に関心がある場合は、条件にスレッド名またはスレッド ID を使用します。 これを行うには、 [ブレークポイント設定] ウィンドウで、 [条件式] ではなく [フィルター] を選択し、フィルターのヒントに従います。 スレッド ID はデバッガーを再起動すると変化するため、アプリ コードでスレッドに名前を付けることができます。
[ブレークポイント設定] ウィンドウを閉じます。
[再起動] ボタンを選択して、デバッグ セッションを再開します。
データ変数の値が 5 になったスレッドで、コードに割り込みます。 [並列ウォッチ] ウィンドウで、現在のデバッガー コンテキストを示す黄色の矢印を探します。
これで、コードをステップオーバー (F10 キー) およびステップイン (F11 キー) して、1 つのスレッドの実行を追跡できます。
ブレークポイントの条件がスレッドに対して一意であり、デバッガーが他のスレッドで他のブレークポイントにヒットしない場合 (無効にすることが必要な場合があります)、他のスレッドに切り替えずに、コードのステップオーバーやステップインを行うことができます。
Note
デバッガーを進めると、すべてのスレッドが実行されます。 ただし、他のスレッドのいずれかがブレークポイントにヒットしない限り、デバッガーが他のスレッドのコードに割り込むことはありません。