Exemplarische Vorgehensweise: Debuggen einer parallelen Anwendung

In dieser exemplarischen Vorgehensweise wird das Debuggen einer parallelen Anwendung mithilfe der Fenster Parallele Aufgaben und Parallele Stapel erläutert. Diese Fenster unterstützen Sie dabei, das Laufzeitverhalten von Code zu verstehen und zu überprüfen, der die Task Parallel Library oder die Concurrency Runtime verwendet. Diese exemplarische Vorgehensweise bietet Beispielcode mit integrierten Haltepunkte. Nach dem Unterbrechen der Ausführung des Codes wird erläutert, wie er mithilfe der Fenster Parallele Aufgaben und Parallele Stapel untersucht wird.

In dieser exemplarischen Vorgehensweise werden die folgenden Aufgaben erklärt:

  • Anzeigen der Aufruflisten aller Threads in einer Ansicht.

  • Anzeigen der Liste der System.Threading.Tasks.Task-Instanzen, die in der Anwendung erstellt werden.

  • Anzeigen der tatsächlichen Aufruflisten mit Aufgaben anstelle von Threads.

  • Navigieren von den Fenstern Parallele Aufgaben und Parallele Stapel aus zu Code.

  • Skalieren der Fenster durch Gruppieren, Vergrößern/Verkleinern und sonstigen entsprechenden Funktionen.

Vorbereitungsmaßnahmen

Auf Ihrem Computer muss Visual Studio 2010 installiert sein.

In dieser exemplarischen Vorgehensweise wird davon ausgegangen, dass Nur eigenen Code aktiviert ist. Klicken Sie im Menü Extras auf Optionen, und erweitern Sie den Knoten Debugging. Wählen Sie Allgemein aus, und wählen Sie dann Nur eigenen Code aktivieren (nur verwaltet) aus. Wenn Sie diese Funktion nicht festlegen, können Sie die vorliegende exemplarische Vorgehensweise zwar verwenden, Ihre Ergebnisse weichen jedoch möglicherweise von den Abbildungen ab.

C#-Beispiel

Wenn Sie das C#-Beispiel verwenden, wird in dieser exemplarischen Vorgehensweise auch davon ausgegangen, dass externer Code ausgeblendet ist. Um die Anzeige von externem Code umzuschalten, klicken Sie mit der rechten Maustaste im Fenster Aufrufliste auf den Tabellenheader Name, und aktivieren bzw. deaktivieren Sie Externen Code anzeigen. Wenn Sie diese Funktion nicht festlegen, können Sie die vorliegende exemplarische Vorgehensweise zwar verwenden, Ihre Ergebnisse weichen jedoch möglicherweise von den Abbildungen ab.

C++-Beispiel

Wenn Sie das C++-Beispiel verwenden, können Sie Verweise auf externen Code in diesem Thema ignorieren. Externer Code bezieht sich ausschließlich auf das C#-Beispiel.

Abbildungen

Die Abbildungen in diesem Thema wurden auf einem Quad-Core-Computer aufgezeichnet, auf dem das C#-Beispiel ausgeführt wird. Sie können diese exemplarische Vorgehensweise auch mit anderen Konfigurationen durcharbeiten. Die Abbildungen unterscheiden sich jedoch möglicherweise von der Anzeige auf Ihrem Computer.

Erstellen des Beispielprojekts

Der Beispielcode in dieser exemplarischen Vorgehensweise ist für eine Anwendung ohne konkrete Aufgaben. Das Ziel besteht lediglich darin zu erläutern, wie mit den Toolfenstern parallele Anwendungen gedebuggt werden.

So erstellen Sie das Beispielprojekt

  1. Zeigen Sie in Visual Studio im Menü Datei auf Neu, und klicken Sie auf Projekt.

  2. Wählen Sie im Bereich Installierte Vorlagen entweder Visual C#, Visual Basic oder Visual C++ aus. Stellen Sie für verwaltete Sprachen sicher, dass im Frameworkfeld .NET Framework 4 angezeigt wird.

  3. Wählen Sie Konsolenanwendung aus, und klicken Sie dann auf OK. Behalten Sie die Debugkonfiguration bei (Standardeinstellung).

  4. Öffnen Sie die CPP-, CS- oder VB-Codedatei im Projekt. Löschen Sie den Dateiinhalt, um eine leere Codedatei zu erstellen.

  5. Fügen Sie den folgenden Code für die ausgewählte Sprache in die leere Codedatei ein.

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. Klicken Sie im Menü Datei auf Alle speichern.

  2. Klicken Sie im Menü Erstellen auf Projektmappe neu erstellen.

    Beachten Sie, dass es vier Aufrufe zu Debugger.Break (DebugBreak im C++-Beispiel) gibt. Daher müssen keine Haltepunkte eingefügt werden; wenn nur die Anwendung ausgeführt wird, hat dies bis zu vier Unterbrechungen im Debugger zur Folge.

Verwenden des Fensters Parallele Stapel: Threadansicht

Klicken Sie im Menü Debuggen auf Debuggen starten. Warten Sie, bis der erste Haltepunkt erreicht wird.

So zeigen Sie die Aufrufliste eines einzelnen Threads an

  1. Zeigen Sie im Menü Debuggen auf Fenster, und klicken Sie auf Threads. Docken Sie das Fenster Threads am unteren Rand von Visual Studio an.

  2. Zeigen Sie im Menü Debuggen auf Fenster, und klicken Sie dann auf Aufrufliste. Docken Sie das Fenster Aufrufliste am unteren Rand von Visual Studio an.

  3. Doppelklicken Sie im Fenster Threads auf einen Thread, um diesen als aktuellen Thread festzulegen. Aktuelle Threads sind durch einen gelben Pfeil gekennzeichnet. Wenn Sie den aktuellen Thread ändern, wird seine Aufrufliste im Fenster Aufrufliste angezeigt.

So untersuchen Sie das Fenster Parallele Stapel

  • Zeigen Sie im Menü Debuggen auf Fenster, und klicken Sie dann auf Parallele Stapel. Vergewissern Sie sich, dass im Feld in der oberen linken Ecke Threads ausgewählt ist.

    Im Fenster Parallele Stapel können Sie mehrere Aufruflisten gleichzeitig in einer Ansicht anzeigen. In der folgenden Abbildung wird das Fenster Parallele Stapel über dem Fenster Aufrufliste angezeigt.

    Fenster "Parallele Stapel" in Ansicht "Threads"

    Die Aufrufliste des Hauptthreads wird in einem Feld angezeigt, während die Aufruflisten für die anderen vier Threads als Gruppe in einem anderen Feld angezeigt werden. Vier Threads werden als Gruppe angezeigt, da für ihre Stapelrahmen dieselben Methodenkontexte verwendet werden; d. h., sie befinden sich in denselben Methoden: A, B und C. Um die Thread-IDs und die Namen der Threads anzuzeigen, die im selben Feld enthalten sind, zeigen Sie mit dem Mauszeiger auf den Header (4 Threads). Der aktuelle Thread wird fett formatiert angezeigt, wie in der folgenden Abbildung veranschaulicht.

    QuickInfo mit Thread-IDs und Namen

    Der gelbe Pfeil gibt den aktiven Stapelrahmen des aktuellen Threads an. Zeigen Sie mit dem Mauszeiger darauf, um weitere Informationen anzuzeigen.

    QuickInfo auf aktivem Stapelrahmen

    Sie können festlegen, wie viele Details für die Stapelrahmen angezeigt werden sollen (Modulnamen, Parametertypen, Parameternamen, Parameterwerte, Zeilennummern und Byte-Offsets), indem Sie mit der rechten Maustaste in das Fenster Aufrufliste klicken.

    Durch eine blaue Hervorhebung um ein Feld wird angegeben, dass der aktuelle Thread zu diesem Feld gehört. Der aktuelle Thread wird auch durch den fett formatierten Stapelrahmen in der QuickInfo angegeben. Wenn Sie im Fenster Threads auf den Hauptthread doppelklicken, können Sie beobachten, wie die blaue Hervorhebung im Fenster Parallele Stapel entsprechend verschoben wird.

    Stapel mit in Blau hervorgehobenem Hauptthread

So setzen Sie die Ausführung bis zum zweiten Haltepunkt fort

  • Um die Ausführung bis zum zweiten Haltepunkt fortzusetzen, klicken Sie im Menü Debuggen auf Weiter. In der folgenden Abbildung wird die Threadstruktur beim zweiten Haltepunkt dargestellt.

    Fenster "Parallele Stapel" mit vielen Verzweigungen

    Beim ersten Haltepunkt gingen alle vier Threads von den Methoden S.A zu S.B zu S.C über. Diese Informationen werden im Fenster Parallele Stapel immer noch angezeigt, die vier Threads wurden jedoch weiter ausgeführt. Ein Thread ist zu S.D und dann zu S.E übergegangen. Ein anderer hat mit S.F, S.G und S.H fortgefahren. Zwei andere wechselten zu S.I und S.J. Von dort ging einer zu S.K über, während der andere zu nicht vom Benutzer stammendem externen Code wechselte.

    Sie können mit dem Mauszeiger auf den Header des Felds zeigen (beispielsweise 1 Thread oder 2 Threads), um die Thread-IDs der Threads anzuzeigen. Sie können mit dem Mauszeiger auf Stapelrahmen zeigen, um die Thread-IDs sowie andere Details zum Rahmen zu überprüfen. Die blaue Hervorhebung gibt den aktuellen Thread an, während mit dem gelben Pfeil der aktive Stapelrahmen des aktuellen Threads angegeben wird.

    Mit dem Faden-Symbol (einander überlappenden blauen und roten Wellenlinien) werden die aktiven Stapelrahmen der nicht aktuellen Threads angegeben. Doppelklicken Sie im Fenster Aufrufliste auf S.B, um zwischen Frames zu wechseln. Im Fenster Parallele Stapel wird der aktuelle Stapelrahmen des aktuellen Threads mit einem grünen gekrümmten Pfeilsymbol angegeben.

    Schalten Sie im Fenster Threads zwischen Threads um. Sie stellen fest, dass die Ansicht des Fensters Parallele Stapel aktualisiert wird.

    Sie können über das Kontextmenü im Fenster Parallele Stapel zu einem anderen Thread oder zu einem anderen Frame eines anderen Threads wechseln. Klicken Sie z. B. mit der rechten Maustaste auf S.J, zeigen Sie auf Zu Rahmen wechseln, und klicken Sie dann auf einen Befehl.

    Parallele Stapel - Ausführungspfad

    Klicken Sie mit der rechten Maustaste auf S.C, und zeigen Sie auf Zu Rahmen wechseln. Ein Befehl ist mit einem Häkchen versehen, das den Stapelrahmen des aktuellen Threads angibt. Sie können zu diesem Frame desselben Threads (nur der grüne Pfeil wird verschoben) wechseln, oder Sie können zum anderen Thread (die blaue Hervorhebung wird ebenfalls verschoben) wechseln. Die folgende Abbildung zeigt das Untermenü.

    Menü "Stapel" mit 2 Optionen auf C, während J aktuell ist

    Wenn ein Methodenkontext nur einem Stapelrahmen zugeordnet ist, wird im Feldheader 1 Thread angezeigt, und Sie können durch Doppelklicken zu diesem wechseln. Wenn Sie auf einen Methodenkontext doppelklicken, dem mehrere Frames zugeordnet sind, wird das Menü automatisch aufgerufen. Wie Sie mit dem Mauszeiger auf die Methodenkontexte zeigen, wird rechts ein schwarzes Dreieck angezeigt. Wenn Sie auf dieses Dreieck klicken, wird ebenfalls das Kontextmenü geöffnet.

    Bei großen Anwendungen mit einer Vielzahl von Threads kann es sich empfehlen, sich nur auf eine Teilmenge von Threads zu konzentrieren. Im Fenster Parallele Stapel können Aufruflisten nur für gekennzeichnete Threads angezeigt werden. Klicken Sie auf der Symbolleiste neben dem Listenfeld auf die Schaltfläche Nur gekennzeichnete Elemente anzeigen.

    Fenster "Leere Stapel" und QuickInfo

    Kennzeichnen Sie anschließend im Fenster Threads die einzelnen Threads, um die zugehörigen Aufruflisten im Fenster Parallele Stapel anzeigen zu lassen. Verwenden Sie zum Kennzeichnen von Threads das Kontextmenü oder die erste Zelle eines Threads. Klicken Sie erneut auf die Symbolleistenschaltfläche Nur gekennzeichnete Elemente anzeigen, um wieder alle Threads anzuzeigen.

So setzen Sie die Ausführung bis zum dritten Haltepunkt fort

  1. Um die Ausführung bis zum dritten Haltepunkt fortzusetzen, klicken Sie im Menü Debuggen auf Weiter.

    Wenn mehrere Threads in derselben Methode enthalten sind, diese sich jedoch nicht am Anfang der Aufrufliste befand, wird die Methode in verschiedenen Feldern angezeigt. Ein Beispiel am aktuellen Haltepunkt ist S.L. Hierin sind drei Threads enthalten, und die Methode wird in drei Feldern angezeigt. Doppelklicken Sie auf S.L.

    Parallele Stapel - Ausführungspfad

    Beachten Sie, dass S.L in den anderen beiden Feldern fett formatiert ist, damit dort ersichtlich ist, an welcher Stelle die Methode außerdem angezeigt wird. Wenn Sie bestimmen möchten, welche Frames S.L aufrufen und welche Frames von der Methode aufgerufen werden, klicken Sie auf der Symbolleiste auf die Schaltfläche Methodenansicht ein- bzw. ausschalten. In der folgenden Abbildung wird die Methodenansicht des Fensters Parallele Stapel angezeigt.

    Stapelfenster in der Methodenansicht

    Das Diagramm wurde für die ausgewählte Methode pivotiert, und diese wurde in einem eigenen Feld in der Mitte der Ansicht positioniert. Die Aufgerufenen und die Aufrufer werden am oberen und unteren Rand angezeigt. Klicken Sie auf die Schaltfläche Methodenansicht ein- bzw. ausschalten, um diesen Modus wieder zu verlassen.

    Das Kontextmenü des Fensters Parallele Stapel weist zudem die folgenden weiteren Elemente auf.

    • Mit Hexadezimale Anzeige wird die Anzeige der Zahlen in den QuickInfos Dezimal- und Hexadezimaldarstellung umgeschaltet.

    • Mit Symbolladeinformationen and Symboleinstellungen werden die entsprechenden Dialogfelder geöffnet.

    • Mit Gehe zu Quellcode und Gehe zu Disassembly wird im Editor zur ausgewählten Methode navigiert.

    • Mit Externen Code anzeigen werden alle Frames angezeigt, auch wenn sie sich nicht im Benutzercode befinden. Das Diagramm wird erweitert, um die zusätzlichen Frames aufzunehmen (die möglicherweise abgeblendet dargestellt werden, da keine Symbole dafür vorhanden sind).

    Wenn Sie bei großen Diagrammen zum nächsten Haltepunkt wechseln, können Sie einen automatischen Bildlauf der Anzeige zum aktiven Stapelrahmen des aktuellen Threads ausführen lassen (d. h. des Threads, der den Haltepunkt zuerst erreicht hat). Vergewissern Sie sich, dass im Fenster Parallele Stapel die Symbolleistenschaltfläche Automatischen Bildlauf zu aktuellem Stapelrahmen durchführen aktiviert ist.

    Automatischer Bildlauf in Fenster "Parallele Stapel"

  2. Führen Sie vor dem Fortfahren im Fenster Parallele Stapel zunächst einen Bildlauf ganz nach links und ganz nach unten aus.

So setzen Sie die Ausführung bis zum vierten Haltepunkt fort

  1. Um die Ausführung bis zum vierten Haltepunkt fortzusetzen, klicken Sie im Menü Debuggen auf Weiter.

    Beachten Sie, wie ein automatischer Bildlauf der Ansicht an die korrekte Position stattfindet. Schalten Sie zwischen Threads im Fenster Threads um, oder schalten Sie zwischen Stapelrahmen im Fenster Aufrufliste um. Sie stellen fest, dass immer ein automatischer Bildlauf der Ansicht zum richtigen Frame erfolgt. Deaktivieren Sie Option Automatischen Bildlauf zu aktuellem Stapelrahmen durchführen, und beobachten Sie den Unterschied.

    Die Vogelperspektive ist auch hilfreich bei großen Diagrammen im Fenster Parallele Stapel. Sie können die Vogelperspektive einblenden, indem Sie auf die Schaltfläche zwischen den Bildlaufleisten in der rechten unteren Ecke des Fensters klicken, wie in der folgenden Abbildung dargestellt.

    Fenster "Parallele Stapel" mit Übersicht

    Sie können das Rechteck verschieben, um einen schnellen Schwenk um das Diagramm auszuführen.

    Eine weitere Möglichkeit, das Diagramm in beliebiger Richtung zu verschieben, besteht darin, auf einen leeren Bereich des Diagramms zu klicken und das Diagramm an die gewünschte Stelle zu ziehen.

    Zum Vergrößern und Verkleinern der Ansicht des Diagramms halten Sie STRG gedrückt, während Sie das Mausrad drehen. Sie können auch auf der Symbolleiste auf die Schaltfläche Zoom klicken und das Tool Zoom verwenden.

    Stapel nebeneinander vergrößert und verkleinert

    Sie können die Stapel auch von oben nach unten angeordnet anstatt von unten nach oben anzeigen. Klicken Sie dazu im Menü Extras auf Optionen, und aktivieren bzw. deaktivieren Sie anschließend die Option unter dem Knoten Debuggen.

  2. Klicken Sie vor dem Fortfahren im Menü Debuggen auf Debugging beenden, um die Ausführung zu beenden.

Verwenden des Fensters Parallele Aufgaben und der Aufgabenansicht des Fensters Parallele Stapel

Es empfiehlt sich, vor dem Fortfahren die früheren Prozeduren abzuschließen.

So starten Sie die Anwendung neu, bis der erste Haltepunkt erreicht wird

  1. Klicken Sie im Menü Debuggen auf Debugging starten, und warten Sie, bis der erste Haltepunkt erreicht wird.

  2. Zeigen Sie im Menü Debuggen auf Fenster, und klicken Sie auf Threads. Docken Sie das Fenster Threads am unteren Rand von Visual Studio an.

  3. Zeigen Sie im Menü Debuggen auf Fenster, und klicken Sie auf Aufrufliste. Docken Sie das Fenster Aufrufliste am unteren Rand von Visual Studio an.

  4. Doppelklicken Sie im Fenster Threads auf einen Thread, um diesen als aktuellen Thread festzulegen. Aktuelle Threads sind durch einen gelben Pfeil gekennzeichnet. Wenn Sie den aktuellen Thread ändern, werden die anderen Fenster aktualisiert. Nun werden Aufgaben untersucht.

  5. Zeigen Sie im Menü Debuggen auf Fenster, und klicken Sie dann auf Parallele Aufgaben. In der folgenden Abbildung wird das Fenster Parallele Aufgaben dargestellt.

    Fenster "Parallele Aufgaben" mit 4 ausgeführten Aufgaben

    Für jede ausgeführte Aufgabe können Sie die zugehörige ID lesen. Diese wird zusammen mit der gleichnamigen Eigenschaft, der ID und dem Namen des ausführenden Threads und ihrer Position zurückgegeben (wenn Sie mit dem Mauszeiger darauf zeigen, wird eine QuickInfo mit der gesamten Aufrufliste angezeigt). Unter der Spalte Aufgabe wird auch die Methode angezeigt, die in die Aufgabe übergeben wurde (mit anderen Worten den Ausgangspunkt).

    Alle Spalten können sortiert werden. Beachten Sie das Sortiersymbol, das Sortierspalte und -richtung angibt. Sie können auch die Anordnung der Spalten ändern, indem Sie sie nach links oder rechts ziehen.

    Der gelbe Pfeil gibt die aktuelle Aufgabe an. Sie können zwischen Aufgaben wechseln, indem Sie auf eine Aufgabe doppelklicken oder indem Sie das Kontextmenü aufrufen. Beim Wechseln zwischen Aufgaben wird der zugrunde liegende Thread zum aktuellen Thread, und die übrigen Fenster werden aktualisiert.

    Wenn Sie manuell von einer Aufgabe zu einer anderen wechseln, wird der gelbe Pfeil verschoben. Ein weißer Pfeil zeigt jedoch weiterhin die Aufgabe an, die die Unterbrechung des Debuggers verursacht hat.

So setzen Sie die Ausführung bis zum zweiten Haltepunkt fort

  • Um die Ausführung bis zum zweiten Haltepunkt fortzusetzen, klicken Sie im Menü Debuggen auf Weiter.

    Zuvor wurden in der Spalte Status alle Aufgaben als Aktiv angezeigt, nun weisen jedoch zwei Aufgaben den Status Wartend auf. Aufgaben können aus vielen anderen Gründen blockiert werden. Zeigen Sie in der Spalte Status auf eine wartende Aufgabe, um den Grund für ihre Blockierung anzuzeigen. In der folgenden Abbildung wartet beispielsweise Aufgabe 3 auf Aufgabe 4.

    Fenster "Parallele Aufgaben" mit 2 wartenden Aufgaben

    Aufgabe 4 wiederum wartet auf einen Monitor, der zu dem Thread gehört, der Aufgabe 2 zugewiesen ist.

    Aufgabenfenster mit wartender Aufgabe und QuickInfo

    Sie können eine Aufgabe kennzeichnen, indem Sie in der ersten Spalte des Fensters Parallele Aufgaben auf das Flag klicken.

    Durch das Kennzeichnen können Sie Aufgaben zwischen den verschiedenen Haltepunkten einer Debugsitzung verfolgen oder Aufgaben filtern, deren Aufruflisten im Fenster Parallele Stapel angezeigt werden.

    Bei Ihrer früheren Verwendung des Fensters Parallele Stapel haben Sie die Anwendungsthreads angezeigt. Rufen Sie das Fenster Parallele Stapel erneut auf, zeigen Sie dieses Mal jedoch die Anwendungsaufgaben an. Wählen Sie dazu im Feld in der oberen linken Ecke Aufgaben auf. In der folgenden Abbildung wird die Aufgabenansicht dargestellt.

    Fenster "Parallele Aufgaben" in der Aufgabenansicht

    Threads, die derzeit keine Aufgaben ausführen, werden in der Aufgabenansicht des Fensters Parallele Stapel nicht angezeigt. Auch für Threads, die Aufgaben ausführen, werden einige der für Aufgaben irrelevanten Stapelrahmen im Stapel von oben und unten gefiltert.

    Rufen Sie das Fenster Parallele Aufgaben erneut auf. Klicken Sie mit der rechten Maustaste auf einen Spaltenheader, um das Kontextmenü für die betreffende Spalte aufzurufen.

    Parallele Aufgaben, columnheader-Menü

    Über das Kontextmenü können Spalten hinzugefügt und entfernt werden. Die Spalte AppDomain ist z. B. nicht ausgewählt. Daher wird sie in der Liste nicht aufgeführt. Klicken Sie auf Übergeordnetes Element. Die Spalte Übergeordnetes Element wird ohne Werte für die vier Aufgaben angezeigt.

So setzen Sie die Ausführung bis zum dritten Haltepunkt fort

  • Um die Ausführung bis zum dritten Haltepunkt fortzusetzen, klicken Sie im Menü Debuggen auf Weiter.

    Eine neue Aufgabe, Aufgabe 5, wird nun ausgeführt, und Aufgabe 4 ist nun wartend. Sie können dies überprüfen, indem Sie im Fenster Status mit dem Mauszeiger auf die wartende Aufgabe zeigen. Beachten Sie in der Spalte Übergeordnetes Element, dass Aufgabe 4 das übergeordnete Element von Aufgabe 5 ist.

    Um die Beziehung zwischen übergeordneten und untergeordneten Daten besser zu veranschaulichen, klicken Sie mit der rechten Maustaste auf den Spaltenheader Übergeordnetes Element, und klicken Sie anschließend auf Ansicht über- und untergeordneter Elemente. Die Anzeige entspricht der folgenden Abbildung:

    Ansicht "Parallele Aufgaben" in Ansicht mit über- und untergeordneten Elementen

    Sie stellen fest, dass Aufgabe 4 und Aufgabe 5 im selben Thread ausgeführt werden. Diese Informationen werden im Fenster Threads nicht angezeigt. Ihre Anzeige an dieser Stelle ist ein weiterer Vorteil des Fensters Parallele Aufgaben. Vergewissern Sie sich davon, indem Sie das Fenster Parallele Stapel öffnen. Stellen Sie sicher, dass Sie Aufgaben anzeigen. Suchen Sie die Aufgaben 4 und 5, indem Sie im Fenster Parallele Aufgaben darauf doppelklicken. In diesem Fall wird die blaue Hervorhebung im Fenster Parallele Stapel aktualisiert. Sie können die Aufgaben 4 und 5 auch suchen, indem Sie die QuickInfos im Fenster Parallele Stapel überprüfen.

    Fenster "Parallele Stapel" in der Aufgabenansicht

    Klicken Sie im Fenster Parallele Stapel mit der rechten Maustaste auf S.P, und klicken Sie anschließend auf Zu Thread wechseln. Das Fenster wechselt zur Threadansicht, und der entsprechende Frame wird angezeigt. Sie können beide Aufgaben im selben Thread sehen.

    Ansicht "Threads" mit hervorgehobenem Thread

    Dies ist ein weiterer Vorteil der Aufgabenansicht im Fenster Parallele Stapel gegenüber dem Fenster Threads.

So setzen Sie die Ausführung bis zum vierten Haltepunkt fort

  • Um die Ausführung bis zum dritten Haltepunkt fortzusetzen, klicken Sie im Menü Debuggen auf Weiter. Klicken Sie auf den Spaltenheader ID, um die Einträge nach ID zu sortieren. Die Anzeige entspricht der folgenden Abbildung:

    Fenster "Parallele Stapel" mit Aufgaben in 4 Zuständen

    Da Aufgabe 5 abgeschlossen wurde, wird sie nicht mehr angezeigt. Wenn dies auf Ihrem Computer nicht der Fall ist und der Deadlock nicht angezeigt wird, führen Sie einen Schritt aus, indem Sie F11 drücken.

    Aufgabe 3 und Aufgabe 4 warten nun auf einander, und für sie ist ein Deadlock eingetreten. Es sind auch fünf neue Aufgaben vorhanden, die untergeordnete Elemente von Aufgabe 2 darstellen und nun geplant sind. Geplante Aufgaben sind Aufgaben, die im Code gestartet, jedoch noch nicht ausgeführt wurden. Daher sind ihre Spalten Speicherort und Threadzuweisung leer.

    Rufen Sie das Fenster Parallele Stapel erneut auf. Der Header der einzelnen Felder weist jeweils eine QuickInfo auf, in der die Thread-IDs und -Namen angegeben werden. Wechseln Sie im Fenster Parallele Stapel zur Aufgabenansicht. Zeigen Sie auf einen Header, um die Aufgaben-ID sowie den Namen und den Status der Aufgabe anzuzeigen, wie in der folgenden Abbildung veranschaulicht.

    Fenster "Parallele Stapel" mit Header-QuickInfo

    Sie können die Aufgaben nach Spalten gruppieren. Klicken Sie im Fenster Parallele Aufgaben mit der rechten Maustaste auf den Spaltenheader Status, und klicken Sie dann auf Gruppieren nach Status. In der folgenden Abbildung ist das nach Status gruppierte Fenster Parallele Aufgaben dargestellt.

    Fenster "Parallele Aufgaben" mit gruppierten Aufgaben

    Sie können auch nach einer beliebigen anderen Spalte gruppieren. Durch das Gruppieren von Aufgaben können sich auf eine Teilmenge von Aufgaben konzentrieren. Jede reduzierbare Gruppe weist eine Anzahl von Elementen auf, die zu einer Gruppe gehören. Sie können auch alle Elemente in der Gruppe schnell kennzeichnen, indem Sie auf die Schaltfläche Flag rechts neben der Schaltfläche Reduzieren klicken.

    Fenster "Parallele Aufgaben" - gruppiert

    Die letzte behandelte Funktion des Fensters Parallele Aufgaben ist das Kontextmenü, das aufgerufen wird, wenn Sie mit der rechten Maustaste auf eine Aufgabe klicken.

    Fenster "Parallele Aufgaben" mit erweitertem Kontextmenü

    Im Kontextmenü sind weitere Befehle enthalten, die vom Status der Aufgabe abhängen. Zu diesen können folgende Befehle zählen: Kopieren, Alle auswählen, Hexadezimale Anzeige, Zu Aufgabe wechseln, Zugewiesenen Thread einfrieren, Alle Threads mit Ausnahme des vorliegenden einfrieren und Zugewiesenen Thread entsperren sowie Flag.

    Sie können den zugrunde liegenden Thread einer Aufgabe oder mehrerer Aufgaben einfrieren, oder Sie können alle Threads außer dem zugewiesenen Thread einfrieren. Ein eingefrorener Thread wird im Fenster Parallele Aufgaben wie im Fenster Threads durch ein blaues Pausensymbol dargestellt.

Zusammenfassung

In dieser exemplarischen Vorgehensweise wurden die Debuggerfenster Parallele Aufgaben und Parallele Stapel veranschaulicht. Verwenden Sie diese Fenster für tatsächliche Projekte, in denen Code mit mehreren Threads enthalten ist. Sie können parallelen Code in C++, C# oder Visual Basic untersuchen.

Siehe auch

Aufgaben

Exemplarische Vorgehensweise: Debuggen einer parallelen Anwendung

Verwenden des Fensters "Parallele Stapel"

Verwenden des Fensters "Parallele Aufgaben"

Konzepte

Parallele Programmierung in .NET Framework

Concurrency Runtime

Weitere Ressourcen

Debuggerwegweiser

Debuggen von verwaltetem Code