Freigeben über


Exemplarische Vorgehensweise: Debuggen einer parallelen Anwendung in Visual Studio (C#, Visual Basic, C++)

In dieser exemplarischen Vorgehensweise wird das Debuggen einer parallelen Anwendung mithilfe der Fenster Parallele Aufgaben und Parallele Stapel erläutert. Mit diesen Fenstern können Sie das Laufzeitverhalten von Code, für den die Task Parallel Library (TPL) oder die Concurrency Runtime genutzt werden, besser verstehen und überprüfen. Diese exemplarische Vorgehensweise bietet Beispielcode mit integrierten Haltepunkte. Es wird erläutert, wie der Code nach Unterbrechung der Ausführung 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 zu Code.

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

Voraussetzungen

Bei dieser exemplarischen Vorgehensweise wird vorausgesetzt, dass die Option Nur eigenen Code aktiviert ist (in neueren Versionen von Visual Studio standardmäßig aktiviert). Wählen Sie im Menü Extras Optionen aus, und erweitern Sie den Knoten Debuggen. 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. Klicken Sie mit der rechten Maustaste im Fenster Aufrufliste auf den Tabellenheader Name, und aktivieren bzw. deaktivieren Sie Externen Code anzeigen, um die Anzeige von externem Code umzuschalten. 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 Artikel ignorieren. Externer Code bezieht sich ausschließlich auf das C#-Beispiel.

Abbildungen

Die Abbildungen in diesem Artikel 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. Der Zweck dieser Übung besteht lediglich darin zu erläutern, wie mit den Toolfenstern parallele Anwendungen gedebuggt werden.

  1. Öffnen Sie Visual Studio, und erstellen Sie ein neues Projekt.

    Wenn das Startfenster nicht geöffnet ist, klicken Sie auf Datei >Startfenster.

    Wählen Sie im Startfenster Neues Projekt aus.

    Wählen Sie im Startfenster Neues Projekt erstellen aus.

    Geben Sie im Fenster Neues Projekt erstellen im Suchfeld Konsole ein. Wählen Sie anschließend in der Liste mit den Sprachen die Option C# , C++ oder Visual Basic und dann in der Liste mit den Plattformen Windows aus.

    Nachdem Sie die Sprach- und Plattformfilter angewendet haben, wählen Sie Konsolen-App für .NET Core oder C++ und dann Weiter aus.

    Hinweis

    Wenn die richtige Vorlage nicht angezeigt wird, öffnen Sie unter Tools>Tools und Features abrufen... den Visual Studio-Installer. Wählen Sie die Workload .NET-Desktopentwicklung oder Desktopentwicklung mit C++ und anschließend Ändern aus.

    Geben Sie im Fenster Neues Projekt konfigurieren einen Namen ein, oder verwenden Sie im Feld Projektname den Standardnamen. Klicken Sie anschließend je nach verfügbarer Option entweder auf Weiter oder auf Erstellen.

    Wählen Sie für .NET Core entweder das empfohlene Zielframework oder .NET 8 aus, und klicken Sie dann auf Erstellen.

    Ein neues Konsolenprojekt wird angezeigt. Nachdem das Projekt erstellt wurde, wird eine Quelldatei angezeigt.

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

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

    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();
    }
    

Nachdem Sie die Codedatei aktualisiert haben, speichern Sie die Änderungen, und erstellen Sie die Lösung.

  1. Wählen Sie im Menü Datei den Befehl Alle speichern aus.

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

Beachten Sie, dass es vier Aufrufe an Debugger.Break (DebugBreak im C++-Beispiel) gibt. Daher müssen Sie keine Breakpoints einfügen. Wenn Sie die Anwendung gerade ausführen, wird sie im Debugger bis zu vier Mal unterbrochen.

Verwenden des Fensters „Parallele Stapel“: Threadansicht

Klicken Sie zunächst im Menü Debuggen auf Debuggen starten. Warten Sie, bis der erste Breakpoint erreicht ist.

Anzeigen der Aufrufliste eines einzelnen Threads

  1. Zeigen Sie im Menü Debuggen auf Fenster, und wählen Sie dann Threads aus. Docken Sie das Fenster Threads am unteren Rand von Visual Studio an.

  2. Zeigen Sie im Menü Debuggen auf Fenster, und wählen Sie dann Aufrufliste aus. 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.

Untersuchen des Fensters „Parallele Stapel“

Zeigen Sie im Menü Debuggen auf Fenster, und wählen Sie dann Parallele Stapel aus. 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.

Screenshot of Threads view in Parallel Stacks window.

Threads view in Parallel Stacks window

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 IDs und Namen der Threads anzuzeigen, die in demselben Feld enthalten sind, zeigen Sie mit dem Mauszeiger auf das Feld mit dem Header ([#] Threads). Der aktuelle Thread wird fett hervorgehoben angezeigt.

Screenshot of Tooltip that shows thread IDs and names.

Tooltip that shows thread IDs and names

Der gelbe Pfeil gibt den aktiven Stapelrahmen des aktuellen Threads an.

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 der Hervorhebungspfeil im Fenster Parallele Stapel entsprechend verschoben wird.

Screenshot of Highlighted main thread in Parallel Stacks window.

Highlighted main thread in Parallel Stacks window

Fortsetzen der Ausführung bis zum zweiten Breakpoint

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

Screenshot of Parallel Stacks window that shows many branches.

Parallel Stacks window that shows many branches

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 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.

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 (miteinander verwobene Linien) 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 gebogenen Pfeilsymbol angegeben.

Hinweis

Eine Beschreibung aller Symbole im Fenster „Parallele Stapel“ finden Sie unter Verwenden des Fensters „Parallele Stapel“.

Schalten Sie im Fenster Threads zwischen Threads um. Sie werden feststellen, 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 wählen Sie dann einen Befehl aus.

Screenshot of Parallel Stacks Path of Execution.

Parallel Stacks Path of Execution

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 wechseln (nur der grüne Pfeil wird verschoben) oder zum anderen Thread (die blaue Hervorhebung wird ebenfalls verschoben). Die folgende Abbildung zeigt das Untermenü.

Screenshot of Stacks menu with 2 options on C while J is current.

Stacks menu with 2 options on C while J is current

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 empfiehlt es sich möglicherweise, sich nur auf eine Teilmenge von Threads zu konzentrieren. Im Fenster Parallele Stapel können Aufruflisten nur für gekennzeichnete Threads angezeigt werden. Verwenden Sie zum Kennzeichnen von Threads das Kontextmenü oder die erste Zelle eines Threads.

Wählen Sie auf der Symbolleiste neben dem Listenfeld die Schaltfläche Nur gekennzeichnete anzeigen aus.

Screenshot of Parallel Stacks window and tooltip.

Parallel Stacks window and tooltip

Jetzt werden nur gekennzeichnete Threads im Fenster Parallele Stapel angezeigt.

Fortsetzen der Ausführung bis zum dritten Breakpoint

  1. Klicken Sie im Menü Debuggen auf Weiter, um die Ausführung bis zum dritten Breakpoint fortzusetzen.

    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.

    Screenshot of Execution path in Parallel Stacks window.

    Execution path in Parallel Stacks window

    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, wählenSie auf der Symbolleiste die Schaltfläche Methodenansicht umschalten aus. In der folgenden Abbildung wird die Methodenansicht des Fensters Parallele Stapel angezeigt.

    Screenshot of Method view in Parallel Stacks window.

    Method view in Parallel Stacks window

    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 jeweils am oberen und unteren Rand angezeigt. Klicken Sie auf die Schaltfläche Methodenansicht umschalten, 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 zwischen Dezimal- und Hexadezimaldarstellung umgeschaltet.

    • Mit Symboleinstellungen werden die entsprechenden Dialogfelder geöffnet.

    • Mit Threads in Quelle anzeigen wird die Anzeige von Threadmarkern in Ihrem Quellcode umgeschaltet, mit denen die Positionen der Threads im Quellcode angegeben wird.

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

  2. Vergewissern Sie sich, dass im Fenster Parallele Stapel die Symbolleistenschaltfläche Autom. Bildlauf zu aktuellem Stapelrahmen aktiviert ist.

    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).

  3. Scrollen Sie vor dem Fortfahren im Fenster Parallele Stapel zunächst ganz nach links und ganz nach unten.

Fortsetzen der Ausführung bis zum vierten Breakpoint

  1. Klicken Sie im Menü Debuggen auf Weiter, um die Ausführung bis zum vierten Breakpoint fortzusetzen.

    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 werden feststellen, dass immer ein automatischer Bildlauf der Ansicht zum richtigen Frame erfolgt. Deaktivieren Sie die Option Autom. Bildlauf zu aktuellem Stapelrahmen, und beobachten Sie den Unterschied.

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

    Screenshot of Birds eye view in Parallel Stacks window.

    Bird's-eye view in Parallel Stacks window

    In der Vogelperspektive können Sie das Rechteck verschieben, um einen schnellen Schwenk für das Diagramm auszuführen.

    Eine weitere Möglichkeit, das Diagramm in beliebiger Richtung zu verschieben, besteht darin, einen leeren Bereich des Diagramms auszuwählen 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.

    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 Debuggen 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 Breakpoint erreicht wird:

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

  2. Zeigen Sie im Menü Debuggen auf Fenster, und wählen Sie dann Threads aus. Docken Sie das Fenster Threads am unteren Rand von Visual Studio an.

  3. Zeigen Sie im Menü Debuggen auf Fenster, und wählen Sie Aufrufliste aus. 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. Als Nächstes untersuchen wir Aufgaben.

  5. Zeigen Sie im Menü Debuggen auf Fenster, und wählen Sie Aufgaben aus. In der folgenden Abbildung ist das Fenster Aufgaben dargestellt.

    Screenshot of Four running tasks in Tasks window.

    Four running tasks in Tasks window

    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, gibt der Pfeilumriss den aktuellen Debuggerkontext für eine nicht aktuelle Aufgabe an.

    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.

Fortsetzen der Ausführung bis zum zweiten Breakpoint

Klicken Sie im Menü Debuggen auf Weiter, um die Ausführung bis zum zweiten Breakpoint fortzusetzen.

Zuvor wurden in der Spalte Status alle Aufgaben als „Aktiv“ angezeigt, aber jetzt weisen zwei Aufgaben den Status „Blockiert“ auf. Aufgaben können aus vielen verschiedenen 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 11 auf Aufgabe 12.

Screenshot of Two waiting tasks in Tasks window.

Zuvor wurden in der Spalte Status alle Aufgaben als „Aktiv“ angezeigt, aber jetzt weisen zwei Aufgaben den Status „Blockiert“ auf. Aufgaben können aus vielen verschiedenen 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 4 auf Aufgabe 5.

Two waiting tasks in Tasks window

Aufgabe 4 wiederum wartet auf einen Monitor, der zu dem Thread gehört, der Aufgabe 2 zugewiesen ist. (Klicken Sie mit der rechten Maustaste auf die Headerzeile, und wählen Sie Spalten>Threadzuweisung aus, um den Wert für die Threadzuweisung für Aufgabe 2 anzuzeigen.)

Waiting task and tooltip in Tasks window

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

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

Als Sie das Fenster Parallele Stapel weiter oben verwendet haben, 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 aus. In der folgenden Abbildung wird die Aufgabenansicht dargestellt.

Screenshot of Tasks view in Parallel Stacks window.

Tasks view in Parallel Stacks window

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.

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

Ü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. Wählen Sie ein übergeordnetes Element aus. Die Spalte Übergeordnetes Element wird ohne Werte für die vier Aufgaben angezeigt.

Fortsetzen der Ausführung bis zum dritten Breakpoint

Klicken Sie im Menü Debuggen auf Weiter, um die Ausführung bis zum dritten Breakpoint fortzusetzen.

Screenshot of Parent-child view in Tasks window.

Beachten Sie in dieser Beispielausführung, dass Aufgabe 11 und Aufgabe 12 unter demselben Thread ausgeführt werden. (Zeigen Sie die Spalte Threadzuweisung an, falls sie ausgeblendet ist.) Diese Informationen werden im Fenster Threads nicht angezeigt. Ihre Anzeige an dieser Stelle ist ein weiterer Vorteil des Fensters Aufgaben. Überprüfen Sie dies, indem Sie das Fenster Parallele Stapel öffnen. Stellen Sie sicher, dass Sie sich Aufgaben ansehen. Sie können die Aufgaben 11 und 12 suchen, indem Sie die QuickInfos im Fenster Parallele Stapel überprüfen.

Task view in Parallel Stacks window

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 die Spaltenkopfzeile, und wählen Sie anschließend Ansicht über- und untergeordneter Elemente aus. Die Anzeige entspricht der folgenden Abbildung:

Parent-child view in Tasks window

Beachten Sie, dass Aufgabe 4 und Aufgabe 5 unter demselben Thread ausgeführt werden. (Zeigen Sie die Spalte Threadzuweisung an, falls sie ausgeblendet ist.) Diese Informationen werden im Fenster Threads nicht angezeigt. Ihre Anzeige an dieser Stelle ist ein weiterer Vorteil des Fensters Aufgaben. Überprüfen Sie dies, indem Sie das Fenster Parallele Stapel öffnen. Stellen Sie sicher, dass Sie sich Aufgaben ansehen. Suchen Sie nach den Aufgaben 4 und 5, indem Sie im Fenster 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.

Task view in Parallel Stacks window

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

Highlighted thread in threads view

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

Fortsetzen der Ausführung bis zum vierten Breakpoint

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

Screenshot of Four task states in Parallel Stacks window.

Aufgabe 10 und Aufgabe 11 warten nun aufeinander und sind blockiert. Jetzt sind auch mehrere neue Vorgänge geplant. Geplante Aufgaben sind Aufgaben, die im Code gestartet, jedoch noch nicht ausgeführt wurden. Daher enthalten ihre Spalten Speicherort und Threadzuweisung Standardmeldungen oder sind leer.

Four task states in Parallel Stacks window

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 sind blockiert. 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.

Header tooltip in Parallel Stacks window

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

Screenshot of Grouped tasks in Tasks window.

Grouped tasks in Tasks window

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.

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

Im Kontextmenü sind weitere Befehle enthalten, die vom Status der Aufgabe abhängen. Zu diesen gehören möglicherweise folgende Befehle: Kopieren, Alle auswählen, Hexadezimale Anzeige, Zu Aufgabe wechseln, Zugewiesenen Thread fixieren, Alle Threads bis auf diesen einfrieren und Zugewiesenen Thread reaktivieren 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 Aufgaben wie im Fenster Threads durch ein blaues Symbol vom Typ Pause 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.