チュートリアル: 並行アプリケーションのデバッグ

このチュートリアルでは、[並列タスク] ウィンドウと [並列スタック] ウィンドウを使用して並行アプリケーションをデバッグする方法について説明します。 これらのウィンドウは、タスク並列ライブラリまたは同時実行ランタイムを使用するコードの実行時の動作の把握や確認に役立ちます。 このチュートリアルには、ブレークポイントが組み込まれたサンプル コードが用意されています。 ブレークポイントでコードの実行が中断されたら、[並列タスク] ウィンドウと [並列スタック] ウィンドウを使用してコードを調べます。

このチュートリアルでは、次の作業について説明します。

  • すべてのスレッドの呼び出し履歴を 1 つのビューに表示する方法

  • アプリケーションで作成された System.Threading.Tasks.Task インスタンスの一覧を表示する方法

  • スレッドではなくタスクの実際の呼び出し履歴を表示する方法

  • [並列タスク] ウィンドウや [並列スタック] ウィンドウからコードに移動する方法

  • グループ化やズームなどの機能を使用してウィンドウの表示を調整する方法

必須コンポーネント

コンピューターに Visual Studio 2010 をインストールしておく必要があります。

このチュートリアルは、[マイ コードのみ] が有効になっていることを前提としています。 [ツール] メニューの [オプション] をクリックし、[デバッグ] ノードを展開して [全般] を選択し、['マイ コードのみ' 設定を有効にする] を選択してください。 この機能が設定されていなくてもこのチュートリアルを使用できますが、図と異なる結果になる可能性があります。

C# のサンプル

C# のサンプルを使用する場合は、外部コードが非表示になっていることも前提になります。 外部コードの表示と非表示を切り替えるには、[呼び出し履歴] ウィンドウの [名前] テーブル ヘッダーを右クリックし、[外部コードの表示] をオンまたはオフにします。 この機能が設定されていなくてもこのチュートリアルを使用できますが、図と異なる結果になる可能性があります。

C++ のサンプル

C++ のサンプルを使用する場合は、このトピックの外部コードに関する記述は無視してかまいません。 外部コードに関する記述が当てはまるのは C# のサンプルだけです。

このトピックの図は、C# のサンプルを実行するクアッド コア コンピューターで記録されたものです。 他の構成を使用してこのチュートリアルを実行することもできますが、画面の表示が図と同じにならない場合があります。

サンプル プロジェクトの作成

このチュートリアルのサンプル コードは、何もしないアプリケーションのコードです。 ここでの目的は、ツール ウィンドウを使用して並行アプリケーションをデバッグする方法を理解することだけです。

サンプル プロジェクトを作成するには

  1. Visual Studio で、[ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。

  2. [インストールされたテンプレート] ペインで、Visual C#、Visual Basic、または Visual C++ を選択します。 マネージ言語については、フレームワークのボックスに .NET Framework 4 が表示されていることを確認します。

  3. [コンソール アプリケーション] を選択し、[OK] をクリックします。 既定のデバッグ構成をそのまま使用します。

  4. プロジェクトで .cpp、.cs、または .vb コード ファイルを開きます。 その内容を削除して、空のコード ファイルを作成します。

  5. 空のコード ファイルに、選択した言語の次のコードを貼り付けます。

Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Diagnostics

Module S

  Sub Main()

    pcount = Environment.ProcessorCount
    Console.WriteLine("Proc count = " + pcount.ToString())
    ThreadPool.SetMinThreads(4, -1)
    ThreadPool.SetMaxThreads(4, -1)

    t1 = New Task(AddressOf A, 1)
    t2 = New Task(AddressOf A, 2)
    t3 = New Task(AddressOf A, 3)
    t4 = New Task(AddressOf 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()
  End Sub
  Sub A(ByVal o As Object)
    B(o)
  End Sub
  Sub B(ByVal o As Object)
    C(o)
  End Sub
  Sub C(ByVal o As Object)

    Dim temp As Integer = o

    Interlocked.Increment(aa)
    While (aa < 4)
    End While

    If (temp = 1) Then
      ' BP1 - all tasks in C
      Debugger.Break()
      waitFor1 = False
    Else
      While (waitFor1)
      End While
    End If
    Select Case temp
      Case 1
        D(o)
      Case 2
        F(o)
      Case 3, 4
        I(o)
      Case Else
        Debug.Assert(False, "fool")
    End Select
  End Sub
  Sub D(ByVal o As Object)
    E(o)
  End Sub
  Sub E(ByVal o As Object)
    ' break here at the same time as H and K
    While (bb < 2)
    End While
    'BP2 - 1 in E, 2 in H, 3 in J, 4 in K
    Debugger.Break()
    Interlocked.Increment(bb)

    'after
    L(o)
  End Sub
  Sub F(ByVal o As Object)
    G(o)
  End Sub
  Sub G(ByVal o As Object)
    H(o)
  End Sub
  Sub H(ByVal o As Object)
    ' break here at the same time as E and K
    Interlocked.Increment(bb)
    Monitor.Enter(mylock)
    While (bb < 3)
    End While
    Monitor.Exit(mylock)

    'after
    L(o)
  End Sub
  Sub I(ByVal o As Object)
    J(o)
  End Sub
  Sub J(ByVal o As Object)

    Dim temp2 As Integer = o

    Select Case temp2
      Case 3
        t4.Wait()
      Case 4
        K(o)
      Case Else
        Debug.Assert(False, "fool2")
    End Select
  End Sub
  Sub K(ByVal o As Object)
    ' break here at the same time as E and H
    Interlocked.Increment(bb)
    Monitor.Enter(mylock)
    While (bb < 3)
    End While
    Monitor.Exit(mylock)

    'after
    L(o)
  End Sub
  Sub L(ByVal oo As Object)
    Dim temp3 As Integer = oo

    Select Case temp3
      Case 1
        M(oo)
      Case 2
        N(oo)
      Case 4
        O(oo)
      Case Else
        Debug.Assert(False, "fool3")
    End Select
  End Sub
  Sub M(ByVal o As Object)
    ' breaks here at the same time as N and Q
    Interlocked.Increment(cc)
    While (cc < 3)
    End While

    'BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
    Debugger.Break()
    Interlocked.Increment(cc)
    While (True)
      Thread.Sleep(500) '  for ever
    End While
  End Sub
  Sub N(ByVal o As Object)
    ' breaks here at the same time as M and Q
    Interlocked.Increment(cc)
    While (cc < 4)
    End While
    R(o)
  End Sub
  Sub O(ByVal o As Object)
    Dim t5 As Task = Task.Factory.StartNew(AddressOf P, TaskCreationOptions.AttachedToParent)
    t5.Wait()
    R(o)
  End Sub
  Sub P()
    Console.WriteLine("t5 runs " + Task.CurrentId.ToString())
    Q()
  End Sub
  Sub Q()
    ' breaks here at the same time as N and M
    Interlocked.Increment(cc)
    While (cc < 4)
    End While
    ' task 5 dies here freeing task 4 (its parent)
    Console.WriteLine("t5 dies " + Task.CurrentId.ToString())
    waitFor5 = False
  End Sub
  Sub R(ByVal o As Object)
    If (o = 2) Then
      ' wait for task5 to die
      While waitFor5
      End While

      '//spin up all procs
      Dim i As Integer
      For i = 0 To pcount - 4 - 1

        Dim t As Task = Task.Factory.StartNew(Sub()
                                                While True

                                                End While
                                              End Sub)
        Console.WriteLine("Started task " + t.Id.ToString())
      Next

      Task.Factory.StartNew(AddressOf T, i + 1 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
      Task.Factory.StartNew(AddressOf T, i + 2 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
      Task.Factory.StartNew(AddressOf T, i + 3 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
      Task.Factory.StartNew(AddressOf T, i + 4 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled
      Task.Factory.StartNew(AddressOf 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(o = 4)
      t3.Wait()
    End If
  End Sub
  Sub T(ByVal o As Object)
    Console.WriteLine("Scheduled run " + Task.CurrentId.ToString())
  End Sub
  Private t1, t2, t3, t4 As Task
  Private aa As Integer = 0
  Private bb As Integer = 0
  Private cc As Integer = 0
  Private waitFor1 As Boolean = True
  Private waitFor5 As Boolean = True
  Private pcount As Integer
  Private mylock As New S2()
End Module

Public Class S2

End Class
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();
}
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <ppl.h>
#include <agents.h>
#include <stdio.h>
#include <concrtrm.h>
#include <vector>

CRITICAL_SECTION cs;

using namespace ::std;
using namespace ::std::tr1;
using namespace ::Concurrency;
task_group task4;
task_group task3;
task_group task2;

volatile long aa = 0;
volatile long bb = 0;
volatile long cc = 0;
static bool waitFor1 = true;
static bool waitFor5 = true;

#pragma optimize("", off)
void Spin()
{
    for(int i=0;i<50*50000;++i);
}
#pragma optimize("",on)

template<class Func>
class RunFunc
{
    Func& m_Func;
    int m_o;
public:
    RunFunc(Func func,int o):m_Func(func),m_o(o){

    };
    void operator()()const{
        m_Func(m_o);
    };
};

void T(int o)
{
    cout << "Scheduled run \n";

};

void R(int o)
{
    if (o == 2)
    {
        while (waitFor5) { ;}
        Spin();

        //use up all processors but 4 by scheduling 4 non-terminating tasks.
        int numProcsToBurn = GetProcessorCount() - 4;
        int i;
        vector<call<int>*> tasks;
        for (i = 0; i <  numProcsToBurn; i++)
        {
            tasks.push_back(new call<int>([](int i){while(true)Spin();}));
            asend(tasks[i],1);
            cout << "Started task  \n";
        }

        task_handle<RunFunc<decltype(T)>> t6(RunFunc<decltype(T)>(T,i + 1 + 5));
        task_handle<RunFunc<decltype(T)>> t7(RunFunc<decltype(T)>(T,i + 2 + 5));
        task_handle<RunFunc<decltype(T)>> t8(RunFunc<decltype(T)>(T,i + 3 + 5));
        task_handle<RunFunc<decltype(T)>> t9(RunFunc<decltype(T)>(T,i + 4 + 5));
        task_handle<RunFunc<decltype(T)>> t10(RunFunc<decltype(T)>(T,i + 5 + 5));
        task2.run(t6);
        task2.run(t7);
        task2.run(t8);
        task2.run(t9);
        task2.run(t10);
    
        //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died  
        DebugBreak();
    }
    else
    {
        if (o!=4)
            throw;

        task3.wait();       
    }
};

void Q()
{
    // breaks here at the same time as N and M
    InterlockedIncrement(& cc);
    while (cc < 4)
    {
        ;
    }
    // task 5 dies here freeing task 4 (its parent)
    cout << "t5 dies\n";
    waitFor5 = false;
};

void P()
{
    cout << "t5 runs\n";
    Q();
};

void O(int o)
{   
    task_group t5;
    t5.run(&P);
    t5.wait();
    R(o);
};

void N(int o)
{
    // breaks here at the same time as M and Q
    InterlockedIncrement(&cc);
    while (cc < 4)
    {
        ;
    }
    R(o);
};

void M(int o)
{
    // breaks here at the same time as N and Q
    InterlockedIncrement(&cc);
    while (cc < 3)
    {
        ;
    }
    //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
    DebugBreak();
    InterlockedIncrement(&cc);
    while (true)
        Sleep(500); // for ever
};

void L(int oo)
{
    int temp3 = oo;

    switch (temp3)
    {
    case 1:
        M(oo);
        break;
    case 2:
        N(oo);
        break;
    case 4:
        O(oo);
        break;
    default:
        throw; //fool3
        break;
    }
}
void K(int o)
{
    // break here at the same time as E and H
    InterlockedIncrement(&bb);
    EnterCriticalSection(&cs);
    while (bb < 3)
    {
        ;
    }
    LeaveCriticalSection(&cs);
    Spin();

    //after
    L(o);
}
void J(int o)
{
    int temp2 = o;

    switch (temp2)
    {
    case 3:
        task4.wait();
        break;
    case 4:
        K(o);
        break;
    default:
        throw; //fool2
        break;
    }
}
static void I(int o)
{
    J(o);
}
static void H(int o)
{
    // break here at the same time as E and K
    InterlockedIncrement(&bb);
    EnterCriticalSection(&cs);
    while (bb < 3)
    {
        ;
    }
    LeaveCriticalSection(&cs);
    Spin();

    //after
    L(o);
}
static void G(int o)
{
    H(o);
}
static void F(int o)
{
    G(o);
}

static void E(int 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  
    Spin(); // for native case only
    DebugBreak();
    InterlockedIncrement(&bb);

    //after
    L(o);

}

static void D(int o)
{
    E(o);
}

static void C(int o)
{
    int temp = o;

    InterlockedIncrement(&aa);
    while (aa < 4)
    {
        ;
    }

    if (temp == 1)
    {
        // BP1 - all tasks in C 
        DebugBreak();
        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:
        throw; //fool
        break;
    }
}
static void B(int o)
{
    C(o);
}

void A(int o)
{
    B(o);
}
int main()
{
    InitializeCriticalSection(&cs);

    task_group tasks;
    task_handle<RunFunc<decltype(A)>> t1(RunFunc<decltype(A)>(A,1));
    tasks.run(t1);
    task_handle<RunFunc<decltype(A)>> t2(RunFunc<decltype(A)>(A,2));
    task2.run(t2);
    task_handle<RunFunc<decltype(A)>> t3(RunFunc<decltype(A)>(A,3));
    task3.run(t3);
    task_handle<RunFunc<decltype(A)>> t4(RunFunc<decltype(A)>(A,4));
    task4.run(t4);
    
    getchar();
    return 1;
}
  1. [ファイル] メニューの [すべてを保存] をクリックします。

  2. [ビルド] メニューの [ソリューションのリビルド] をクリックします。

    このコードには、Debugger.Break の呼び出しが 4 つあります (C++ サンプルの場合は DebugBreak)。したがって、ブレークポイントを挿入する必要はありません。そのまま実行するだけで、デバッガーでアプリケーションの実行が 4 回中断されます。

[並列スタック] ウィンドウの使用: スレッド ビュー

[デバッグ] メニューの [デバッグ開始] をクリックします。 1 つ目のブレークポイントにヒットするまで待ちます。

1 つのスレッドの呼び出し履歴を表示するには

  1. [デバッグ] メニューの [ウィンドウ] をポイントし、[スレッド] をクリックします。 [スレッド] ウィンドウを Visual Studio の下部にドッキングします。

  2. [デバッグ] メニューの [ウィンドウ] をポイントし、[呼び出し履歴] をクリックします。 [呼び出し履歴] ウィンドウを Visual Studio の下部にドッキングします。

  3. [スレッド] ウィンドウのスレッドをダブルクリックして、そのスレッドを現在のスレッドにします。 現在のスレッドには黄色の矢印が表示されます。 現在のスレッドを変更すると、そのスレッドの呼び出し履歴が [呼び出し履歴] ウィンドウに表示されます。

[並列スタック] ウィンドウを調べるには

  • [デバッグ] メニューの [ウィンドウ] をポイントし、[並列スタック] をクリックします。 左上隅のボックスで [スレッド] が選択されていることを確認します。

    [並列スタック] ウィンドウを使用すると、複数の呼び出し履歴を 1 つのビューに同時に表示できます。 次の図では、[呼び出し履歴] ウィンドウの上に [並列スタック] ウィンドウが表示されています。

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

    一方のボックスにはメイン スレッドの呼び出し履歴が表示され、もう一方のボックスには他の 4 つのスレッドの呼び出し履歴がグループ化されて表示されています。 4 つのスレッドがグループ化されているのは、これらのスレッドのスタック フレームが同じメソッド コンテキストを共有している (A、B、C という同じメソッドにある) からです。 同じボックスを共有しているスレッドのスレッド ID と名前を表示するには、ヘッダー ("4 個のスレッド") をポイントします。 次の図のように、現在のスレッドが太字で表示されます。

    スレッド 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 に進み、もう一方は非ユーザー外部コードに進んでいます。

    "1 個のスレッド""2 個のスレッド" などのボックス ヘッダーをポイントすると、スレッドのスレッド ID が表示されます。 スタック フレームをポイントすると、スレッド ID とフレームのその他の詳細が表示されます。 青の強調表示は現在のスレッドを示し、黄色の矢印は現在のスレッドのアクティブなスタック フレームを示します。

    より糸のアイコン (青と赤の波線が重なったアイコン) は、現在のスレッドではないスレッドのアクティブなスタック フレームを示します。 [呼び出し履歴] ウィンドウで S.B をダブルクリックしてフレームを切り替えます。 [並列スタック] ウィンドウで、現在のスレッドの現在のスタック フレームが緑色の曲線矢印のアイコンによって示されます。

    [スレッド] ウィンドウでスレッドを切り替えて、[並列スタック] ウィンドウのビューが更新されることを確認します。

    別のスレッドに切り替えたり、別のスレッドの別のフレームに切り替えたりするには、[並列スタック] ウィンドウのショートカット メニューを使用することもできます。 たとえば、S.J を右クリックし、[フレームに切り替え] をポイントして、いずれかのコマンドをクリックします。

    並列スタックの実行パス

    S.C を右クリックし、[フレームに切り替え] をポイントします。 チェック マークが付いているコマンドがあります。これは、現在のスレッドのスタック フレームを示します。 その同じスレッドのフレームに切り替えることも (緑色の矢印のみが移動します)、他のスレッドに切り替えることもできます (青の強調表示も移動します)。 次の図は、このサブメニューを示しています。

    J が最新の際に C に 2 つのオプションがあるスタック メニュー

    メソッド コンテキストに関連付けられているスタック フレームが 1 つしかない場合は、ボックス ヘッダーに "1 個のスレッド" と表示されます。この場合は、メソッド コンテキストをダブルクリックするだけでそのフレームに切り替えることができます。 複数のフレームが関連付けられているメソッド コンテキストをダブルクリックすると、自動的にメニューがポップアップ表示されます。 メソッド コンテキストをポイントすると右側に表示される黒い三角形を クリックして、このショートカット メニューを表示することもできます。

    多数のスレッドを持つ大規模なアプリケーションでは、一部のスレッドのみに焦点を絞ることもできます。 [並列スタック] ウィンドウでは、フラグが設定されたスレッドの呼び出し履歴のみを表示することができます。 ツール バーで、リスト ボックスの横にある [フラグが設定されたもののみを表示] ボタンをクリックします。

    空のスタック ウィンドウおよびツールヒント

    次に、[スレッド] ウィンドウでスレッドに 1 つずつフラグを設定し、それらの呼び出し履歴が [並列スタック] ウィンドウに表示されることを確認します。 スレッドにフラグを設定するには、ショートカット メニューを使用するか、スレッドの最初のセルを使用します。 ツール バーの [フラグが設定されたもののみを表示] ボタンをもう一度クリックして、すべてのスレッドを表示します。

3 つ目のブレークポイントまで実行を再開するには

  1. 3 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] をクリックします。

    同じメソッドに複数のスレッドがあるが、そのメソッドが呼び出し履歴の先頭にない場合、そのメソッドは複数のボックスに表示されます。 たとえば、現在のブレークポイントの S.L がこれに当たります。このメソッドは 3 つのスレッドを持ち、3 つのボックスに表示されています。 S.L をダブルクリックします。

    並列スタックの実行パス

    他の 2 つのボックスで S.L が太字になっていることに注目してください。これにより、このメソッドが他にどこにあるのかがわかります。 S.L がどのフレームから呼び出され、どのフレームを呼び出すのかを確認するには、ツール バーの [メソッド ビューの切り替え] ボタンをクリックします。 次の図は、[並列スタック] ウィンドウのメソッド ビューを示しています。

    メソッド ビューのスタック ウィンドウ

    選択したメソッドを中心とする図が表示されます。選択したメソッドが固有のボックスに入れられて、ビューの中心に配置されます。 その上下には、呼び出し先と呼び出し元が表示されます。 もう一度 [メソッド ビューの切り替え] ボタンをクリックして、このモードを終了します。

    [並列スタック] ウィンドウのショートカット メニューには、次のような項目も含まれています。

    • [16 進数で表示]: ツールヒントの数値を 10 進数または 16 進数に切り替えます。

    • [シンボル読み込み情報] および [シンボルの設定]: それぞれのダイアログ ボックスを開きます。

    • [ソース コードへ移動] および [逆アセンブルを表示]: エディターで、選択したメソッドに移動します。

    • [外部コードの表示]: ユーザー コード以外のものも含めてすべてのフレームを表示します。 この項目を選択し、図に追加のフレームが表示されることを確認してください (シンボルがないために選択不可になっている可能性があります)。

    大きな図がある場合は、次のブレークポイントにステップするときに、現在のスレッド (最初にブレークポイントにヒットしたスレッド) のアクティブなスタック フレームまでビューが自動的にスクロールするようにすることができます。 [並列スタック] ウィンドウで、ツール バーの [現在のスタック フレームに自動スクロール] ボタンがオンになっていることを確認します。

    並列スタック ウィンドウの自動スクロール

  2. 次に進む前に、[並列スタック] ウィンドウを一番左の一番下までスクロールしておきます。

4 つ目のブレークポイントまで実行を再開するには

  1. 4 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] をクリックします。

    ビューが適切な位置まで自動的にスクロールすることに注目してください。 [スレッド] ウィンドウでスレッドを切り替えたり、[呼び出し履歴] ウィンドウでスタック フレームを切り替えたりして、ビューが常に適切なフレームまで自動的にスクロールすることを確認します。 さらに、[現在のツール フレームに自動スクロール] オプションをオフにして違いを確認します。

    [並列スタック] ウィンドウに大きな図がある場合は、バード アイ ビュー も便利です。 バード アイ ビュー を表示するには、次の図に示すように、ウィンドウの右下のスクロール バーの間にあるボタンをクリックします。

    鳥瞰図がある並列スタック ウィンドウ

    四角形の枠を移動することにより、図のさまざまな場所をすばやく表示できます。

    図を任意の方向に移動するには、図の空白の領域をクリックして目的の方向にドラッグすることもできます。

    図を拡大/縮小するには、Ctrl キーを押しながらマウス ホイールを動かします。 または、ツール バーの [ズーム] ボタンをクリックしてズーム ツールを使用します。

    左右に並べて表示したスタックの拡大/縮小

    スタックを下から上の方向ではなく上から下の方向で表示することもできます。[ツール] メニューの [オプション] をクリックし、[デバッグ] ノードのオプションをオンまたはオフにします。

  2. 次に進む前に、[デバッグ] メニューの [デバッグの停止] をクリックして実行を終了します。

[並列タスク] ウィンドウと、[並列スタック] ウィンドウのタスク ビューの使用

先に進む前に前の手順を完了することをお勧めします。

1 つ目のブレークポイントにヒットするまでアプリケーションの実行を再開するには

  1. [デバッグ] メニューの [デバッグ開始] をクリックし、1 つ目のブレークポイントにヒットするまで待ちます。

  2. [デバッグ] メニューの [ウィンドウ] をポイントし、[スレッド] をクリックします。 [スレッド] ウィンドウを Visual Studio の下部にドッキングします。

  3. [デバッグ] メニューの [ウィンドウ] をポイントし、[呼び出し履歴] をクリックします。 [呼び出し履歴] ウィンドウを Visual Studio の下部にドッキングします。

  4. [スレッド] ウィンドウのスレッドをダブルクリックして、そのスレッドを現在のスレッドにします。 現在のスレッドには黄色の矢印が表示されます。 現在のスレッドを変更すると、他のウィンドウが更新されます。 次に、タスクについて調べます。

  5. [デバッグ] メニューの [ウィンドウ] をポイントし、[並列タスク] をクリックします。 次の図は、[並列タスク] ウィンドウを示しています。

    4 つの実行中のタスクがある並列タスク ウィンドウ

    実行中の各タスクについて、タスクの ID (同名のプロパティから返されます)、そのタスクを実行しているスレッドの ID と名前、およびタスクの場所 (ここをポイントすると、ツールヒントに呼び出し履歴全体が表示されます) が表示されます。 また、[タスク] 列には、そのタスクに渡されたメソッド (開始点) が表示されます。

    この一覧は、任意の列で並べ替えることができます。 並べ替えグリフにより、並べ替えの列と方向が示されます。 列を左右にドラッグして列の順序を変更することもできます。

    黄色の矢印は現在のタスクを示します。 タスクを切り替えるには、タスクをダブルクリックするか、ショートカット メニューを使用します。 タスクを切り替えると、基になるスレッドが現在のスレッドになり、他のウィンドウが更新されます。

    タスクを手動で切り替えると黄色の矢印が移動しますが、デバッガーが中断されたタスクは引き続き白の矢印によって示されます。

2 つ目のブレークポイントまで実行を再開するには

  • 2 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] をクリックします。

    先ほどは、[状態] 列ですべてのタスクが "実行中" になっていましたが、現在は 2 つのタスクが "待機中" になっています。 タスクがブロックされるのにはさまざまな理由があります。 [状態] 列で待機中のタスクをポイントすると、タスクがブロックされている理由を確認できます。 たとえば、次の図では、タスク 3 はタスク 4 を待機しています。

    2 つの待機中のタスクがある並列タスク ウィンドウ

    一方、タスク 4 は、タスク 2 に割り当てられているスレッドが所有するモニターを待機しています。

    待機中のタスクがあるタスク ウィンドウとツールヒント

    [並列タスク] ウィンドウの最初の列のフラグをクリックすると、タスクにフラグを設定できます。

    フラグを設定することにより、同じデバッグ セッションの異なるブレークポイントの間でタスクを追跡したり、[並列スタック] ウィンドウに呼び出し履歴を表示するタスクをフィルター選択したりすることができます。

    先ほど [並列スタック] ウィンドウを使用したときにはアプリケーションのスレッドを表示しましたが、 今度は [並列スタック] ウィンドウでアプリケーションのタスクを表示します。 そのためには、左上のボックスで [タスク] を選択します。 次の図は、タスク ビューを示しています。

    タスク ビューの並列タスク ウィンドウ

    [並列スタック] ウィンドウのタスク ビューには、現在タスクを実行していないスレッドは表示されません。 タスクを実行するスレッドについても、タスクに関連しないスタック フレームはスタックの上下からフィルターで除外されます。

    再び [並列タスク] ウィンドウを表示します。 任意の列ヘッダーを右クリックして、その列のショートカット メニューを表示します。

    並列タスクの列ヘッダー メニュー

    ショートカット メニューを使用して、列を追加したり削除したりすることができます。 たとえば、[AppDomain] 列は選択されていないため、一覧に表示されていません。 [Parent] をクリックします。 [Parent] 列が表示されますが、4 つのタスクのいずれにもこの列の値はありません。

3 つ目のブレークポイントまで実行を再開するには

  • 3 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] をクリックします。

    新しいタスク (タスク 5) が実行中になり、タスク 4 は待機中になっています。 [状態] 列でこの待機中のタスクをポイントすると、理由が表示されます。 [Parent] 列を見ると、タスク 4 はタスク 5 の親であることがわかります。

    この親子関係をわかりやすく表示するために、[Parent] 列の列ヘッダーを右クリックし、[親子ビュー] をクリックします。 次の図のような画面が表示されます。

    親子ビューの並列タスク ビュー

    タスク 4 とタスク 5 が同じスレッドで実行されていることに注目してください。 この情報は、[スレッド] ウィンドウには表示されません。この情報を表示できるのも、[並列タスク] ウィンドウの利点の 1 つです。 この情報を確認するために、[並列スタック] ウィンドウを表示します。 ビューが [タスク] になっていることを確認します。 タスク 4 とタスク 5 を見つけるには、[並列タスク] ウィンドウでそれらをダブルクリックします。 これにより、[並列スタック] ウィンドウの青の強調表示が更新されます。 [並列スタック] ウィンドウでツールヒントを調べてタスク 4 とタスク 5 を見つけることもできます。

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

    [並列スタック] ウィンドウで、S.P を右クリックし、[スレッドに移動] をクリックします。 ウィンドウがスレッド ビューに切り替わり、対応するフレームが表示されます。 両方のタスクが同じスレッドにあることがわかります。

    強調表示されたスレッドがあるスレッド ビュー

    これもまた、[スレッド] ウィンドウにはない、[並列スタック] ウィンドウのタスク ビューならではの利点です。

4 つ目のブレークポイントまで実行を再開するには

  • 4 つ目のブレークポイントにヒットするまで実行を再開するには、[デバッグ] メニューの [続行] をクリックします。 [ID] 列の列ヘッダーをクリックして ID で並べ替えます。 次の図のような画面が表示されます。

    4 つの状態のタスクがある並列スタック ウィンドウ

    タスク 5 は完了したため、もう表示されません。 お使いのコンピューターでそうならず、デッドロックが表示されていない場合は、F11 キーを押して 1 回ステップ実行してください。

    タスク 3 とタスク 4 は互いを待機しており、デッドロックの状態になっています。 また、5 つの新しいタスクがあります。これらはタスク 2 の子で、現在スケジュールされているタスクです。 スケジュールされたタスクとは、コードで開始されているがまだ実行されていないタスクです。 したがって、[場所] 列と [スレッドの割り当て] 列が空になっています。

    再び [並列スタック] ウィンドウを表示します。 各ボックスのヘッダーをポイントすると、ツールヒントにスレッドの ID と名前が表示されます。 [並列スタック] ウィンドウのタスク ビューに切り替えます。 次の図のようにヘッダーをポイントして、タスクの ID、名前、および状態を確認します。

    ヘッダー ツールヒントがある並列スタック ウィンドウ

    タスクを列でグループ化することもできます。 [並列タスク] ウィンドウで、[状態] 列の列ヘッダーを右クリックし、[状態でグループ化] をクリックします。 次の図は、状態でグループ化された [並列タスク] ウィンドウを示しています。

    グループ化されたタスクがある並列タスク ウィンドウ

    ほかにも任意の列でグループ化できます。 タスクをグループ化すると、一部のタスクに焦点を絞ることができます。 各グループは折りたたみ可能で、それぞれにグループ化されている項目の数が表示されます。 また、[折りたたみ] ボタンの右にある [フラグ設定] ボタンをクリックすることで、グループ内のすべての項目にすばやくフラグを設定できます。

    グループ化された並列タスク ウィンドウ

    最後に、[並列タスク] ウィンドウでタスクを右クリックすると表示されるショートカット メニューを確認します。

    コンテキスト メニューが展開された並列タスク ウィンドウ

    このショートカット メニューに表示されるコマンドは、タスクの状態によって異なります。 たとえば、[コピー][すべて選択][16 進数で表示][タスクに切り替え][割り当てられたスレッドを凍結][これ以外のすべてのスレッドを凍結][割り当てられたスレッドの凍結を解除][フラグ設定] などのコマンドが表示されます。

    1 つまたは複数のタスクの基になるスレッドを凍結したり、割り当てられたスレッド以外のすべてのスレッドを凍結したりすることができます。 凍結されたスレッドは、[並列タスク] ウィンドウでも、[スレッド] ウィンドウと同じように青の一時停止のアイコンで表されます。

概要

このチュートリアルでは、デバッガーの [並列タスク] ウィンドウと [並列スタック] ウィンドウについて説明しました。 マルチスレッド コードを使用する実際のプロジェクトでこれらのウィンドウを使用してみてください。 C++、C#、または Visual Basic で記述された並列コードを調べることができます。

参照

処理手順

チュートリアル: 並行アプリケーションのデバッグ

概念

.NET Framework の並列プログラミング

同時実行ランタイム

その他の技術情報

デバッガーのロードマップ

マネージ コードのデバッグ

[並列スタック] ウィンドウの使用

[並列タスク] ウィンドウの使用