Visual Studio 2010のマルチスレッドアプリケーションのデバッグガイド

この記事では、 Parallel TasksウィンドウとParallel Stacksウィンドウを使用して、Visual Studio 2010でマルチスレッドアプリケーションをデバッグする方法を説明します。 これらのウィンドウは、マルチスレッドアプリケーションの実行構造を理解し、 タスクパラレルライブラリを使用するコードの正しい動作を確認するのに役立ちます。

私たちは学びます:


注意、たくさんの写真

準備する

テストには、VS 2010が必要です。この記事の画像は、Intel Core i3プロセッサを使用して取得したものです

プロジェクトコード

VBおよびC ++言語のコードは、 このページにあります。

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(<font color="#0000ff">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();
}

*このソースコードは、 ソースコードハイライターで強調表示されました。


並列スタックウィンドウ:スレッドビュー


ステップ1

新しいプロジェクトのスタジオにコードをコピーし、デバッグモード(F5)で実行します。 プログラムは、最初のブレークポイントでコンパイル、開始、停止します。
メニューデバッグ→ウィンドウで、 並列スタックをクリックします このウィンドウを使用して、並列スレッドのいくつかの呼び出しスタックを確認できます。 次の図は、最初の停止点でのプログラムの状態を示しています。 [ デバッグ ] →[ウィンドウ ]メニューの同じ場所で、[ コールスタック]ウィンドウがオンになります 。 これらのウィンドウは、プログラムのデバッグ中にのみ使用できます。 コードを書いている時点では、それらは単に表示されていません。

画像

図では、4つのスレッドがグループ化されています。これは、それらのスタックフレームが同じメソッドコンテキストに属しているためです。つまり、同じメソッド(A、B、C)であるということです。 スレッドIDを確認するには、「4スレッド」ヘッダーをポイントする必要があります。 現在のストリームは太字で強調表示されます 。 黄色の矢印は、現在のスレッドのアクティブなスタックフレームを示します。 追加情報を取得するには、マウスを移動する必要があります。

画像

不要な情報を削除したり、有効にしたりするには(たとえば、モジュール名、シフト、パラメーター名、およびそれらのタイプなど)、 Call Stackウィンドウのテーブルヘッダーを右クリックする必要があります(Windows環境全体で同様に行われます)。

青色の枠は、現在のスレッド(太字で強調表示されている)がこれらのスレッドの一部であることを意味します。

ステップ2

プログラムを2番目の停止ポイント(F5)まで続行します。 次のスライドは、2番目のポイントでのフローの状態を示しています。

画像

最初のステップでは、4つのスレッドがメソッドAB、およびCから来ました この情報は[ Parallel Stacks]ウィンドウで引き続き利用できますが、これらの4つのストリームはさらに開発されています。 1つのスレッドがDで続き次にEで続きました もう一方はFG 、そしてHです。 他の2つはIJにあり、そこから1つはKに向かい、もう1つはユーザー以外の外部コードに進みました。

ストリームをダブルクリックすると、別のストリームに切り替えることができます。 メソッドKが見たいです これを行うには、 MyCalss.Kをダブルクリックします

画像

Parallel Stacksは情報を表示し、スタジオのデバッガーはこの場所のコードを表示します。 [ メソッドビューの切り替え]をクリックし、 Kまでのメソッドの履歴(階層)の画像を確認します

画像

ステップ3

3つの割り込みまでデバッグを続けます。

画像

複数のスレッドが同じメソッドに到達したが、そのメソッドが呼び出しスタックの先頭にない場合、メソッドLで発生したように、異なるフレームに表示されます
Lメソッドをダブルクリックします。 こんな写真が撮れます

画像

メソッドLは、他の2つのフレームでも太字であるため、まだ表示されている場所を確認できます。 どのフレームがメソッドLを呼び出すかを確認するには、表示モードを切り替えます( メソッドビューの切り替え )。 次のものが得られます。

画像

コンテキストメニューには、「 16進表示 」や「 外部コードの 表示 」などの項目があります。 後者のモードを有効にすると、ダイアグラムは前のものよりも大きくなり、 非ユーザーコードに関する情報が含まれます

画像

ステップ4

4回目の中断までプログラムを継続します。

今回は、図が非常に大きくなり、自動スクロールが救助に来て、すぐに適切な場所に移動します。 また、鳥瞰図は、大きなチャートをすばやくナビゲートするのに役立ちます。 (右下の小さなボタン)。 自動ズームとその他の喜びは、本当に大きなマルチスレッドアプリケーションをナビゲートするのに役立ちます。

画像

並列タスクウィンドウおよび並列スタックウィンドウのタスクビュー


ステップ1

プログラムを終了(Shift + F5)またはデバッグメニューで。 前の例で実験したすべての余分なウィンドウを閉じて、新しいウィンドウを開きます: デバッグ→ウィンドウ→スレッドデバッグ→ウィンドウ→呼び出しスタック 、そしてそれらをスタジオの端まで彫刻します。 また、 [デバッグ]-> [Windows]-> [並列タスク]を開きます[並列タスク]ウィンドウで何が起こったのかを次に示します。

画像

起動された各タスクには、同じ名前のタスクプロパティの値を返すID、タスクの場所( コンソールにマウスを合わせるとコールスタック全体が表示されます)、およびタスクの開始点として採用されたメソッド(タスクの開始)があります。

ステップ2

前回は、すべてのタスクが進行中としてマークされていましたが、現在2つのタスクがさまざまな理由でブロックされています。 理由を見つけるには、タスクにカーソルを合わせる必要があります。

画像

タスクにフラグを立てて、さらにステータスを監視できます。

前の例で使用した[ 並列スタック]ウィンドウには、ストリームからタスクへのビューの切り替えがあります(左上)。 ビューをタスクに切り替える

画像

ステップ3

画像

スクリーンショットからわかるように、新しいタスク5は完了し、タスク3と4は停止しています。 テーブルの外観を変更することもできます-列ヘッダーを右クリックします。 タスクの先祖(親)の表示を有効にすると、タスク番号5の先祖が誰であるかがわかります。しかし、関係をよりよく視覚化するために、 親→親子ビュー列で特別なビューRMBを有効にできます。

画像

[ 並列タスク]ウィンドウと[ 並列スタック]ウィンドウは同期されます。 したがって、どのタスクがどのスレッドで実行されているかを確認できます。 たとえば、タスク4。[ 並列タスク]ウィンドウでタスク4をダブルクリックすると、このクリックは[ 並列スタック]ウィンドウと同期し、そのような画像が表示されます。

画像

[ 並列スタック]ウィンドウのタスクモードでは、ストリームに移動できます。 このためには、メソッド上でRMBおよびGo To Thread 。 この例では、 Oメソッドに何が起こるかを確認します。

画像

ステップ4

次のポイントに進みます。 次に、タスクをIDでソートし、次のように表示します

画像

リストにはタスク5はありません。すでに完了しているためです。 タスク3と4はお互いを待っており、行き詰まっています。 タスク2からさらに5つの新しいタスクがあり、現在実行がスケジュールされています。

並列スタックに戻ります。 各タブレットのタイトルにあるツールチップは、ブロックされているタスクの数、予想されるタスクの数、完了しているタスクの数を示します。

画像

タスクリスト内のタスクはグループ化できます。 たとえば、ステータスでグループ化する-ステータス列でRMB、ステータスでグループ化する 。 スクリーンショットの結果:

画像

[並列タスク]ウィンドウには、さらにいくつかの可能性があります。コンテキストメニューで、タスクをフリーズしたり、タスクのメインスレッドをフリーズしたりできます。

おわりに


これら2つの強力なツールを使用すると、大規模で複雑なマルチスレッドアプリケーションをデバッグできます。 結果とタスクが完了する順序を確認します。 タスクの正しい順序を計画します。 マルチスレッドプログラムの作業の誤解に関連するエラーの最小数でプログラムをビルドします。

文学



映像



ダニエルモスブログ



マルチスレッドアプリケーションでの注意とエラーの減少に感謝します:)

Source: https://habr.com/ru/post/J97817/


All Articles