Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Tutorial wird gezeigt, wie Sie die Threads-Ansicht der Parallel Stacks-Fenster zum Debuggen einer Multithread-Anwendung verwenden. Dieses Fenster hilft Ihnen, das Laufzeitverhalten von Multithreadcode zu verstehen und zu überprüfen.
Die Threads-Ansicht wird für C#, C++ und Visual Basic unterstützt. Beispielcode wird für C# und C++ bereitgestellt, aber einige der Codeverweise und Illustrationen gelten nur für den C#-Beispielcode.
Die Threads-Ansicht hilft Ihnen bei:
Zeigen Sie Aufrufstapelvisualisierungen für mehrere Threads an, die ein vollständiges Bild des App-Zustands als das Fenster "Aufrufstapel" bieten, in dem nur der Aufrufstapel für den aktuellen Thread angezeigt wird.
Helfen Sie beim Identifizieren von Problemen wie blockierten oder gesperrten Threads.
Multithread-Aufrufstapel
Identische Abschnitte des Aufrufstapels werden gruppiert, um die Visualisierung für komplexe Apps zu vereinfachen.
Die folgende konzeptionelle Animation zeigt, wie die Gruppierung auf Aufrufstapel angewendet wird. Es werden nur identische Segmente eines Anrufstapels gruppiert. Bewegen Sie den Mauszeiger über einen gruppierten Aufrufstapel, um die Threads zu identifizieren.
Beispielcodeübersicht (C#, C++)
Der Beispielcode in diesem Tutorial ist für eine Anwendung, die einen Tag im Leben eines Gorillas simuliert. Der Zweck der Übung besteht darin, zu verstehen, wie Sie die Threads-Ansicht des Fensters "Parallele Stapel" verwenden, um eine Multithreadanwendung zu debuggen.
Das Beispiel enthält ein Beispiel für einen Deadlock, der auftritt, wenn zwei Threads aufeinander warten.
Um den Aufrufstapel intuitiv zu gestalten, führt die Beispiel-App die folgenden sequenziellen Schritte aus:
- Erstellt ein Objekt, das einen Gorilla darstellt.
- Gorilla wacht auf.
- Gorilla geht morgens zu Fuß.
- Gorilla findet Bananen im Dschungel.
- Gorilla isst.
- Gorilla betreibt Affengeschäfte.
Erstellen des Beispielprojekts
So erstellen Sie das Projekt:
Öffnen Sie Visual Studio, und erstellen Sie ein neues Projekt.
Wenn das Startfenster nicht geöffnet ist, wählen Sie Datei>Startfensteraus.
Wählen Sie im Startfenster " Neues Projekt" aus.
Geben Sie im Fenster Neues Projekt erstellen im Suchfeld Konsole ein. Wählen Sie als Nächstes C# oder C++ aus der Liste "Sprache" und dann "Windows " aus der Liste "Plattform" aus.
Nachdem Sie die Sprach- und Plattformfilter angewendet haben, wählen Sie die Konsolen-App für die ausgewählte Sprache und dann "Weiter" aus.
Note
Wenn die richtige Vorlage nicht angezeigt wird, wechseln Sie zu Tools>»Tools und Features abrufen«, wodurch das Visual Studio-Installationsprogramm geöffnet wird. Wählen Sie beispielsweise die Workload .NET-Desktopentwicklung aus, und klicken Sie anschließend auf Ändern.
Geben Sie im Fenster " Neues Projekt konfigurieren " einen Namen ein, oder verwenden Sie den Standardnamen im Feld "Projektname ". Klicken Sie dann auf Weiter.
Wählen Sie für ein .NET-Projekt entweder das empfohlene Zielframework oder .NET 8 und dann "Erstellen" aus.
Ein neues Konsolenprojekt wird angezeigt. Nachdem das Projekt erstellt wurde, wird eine Quelldatei angezeigt.
Öffnen Sie die Codedatei .cs (oder .cpp) im Projekt. Löschen Sie den Inhalt, um eine leere Codedatei zu erstellen.
Fügen Sie den folgenden Code für die ausgewählte Sprache in die leere Codedatei ein.
using System.Diagnostics; namespace Multithreaded_Deadlock { class Jungle { public static readonly object tree = new object(); public static readonly object banana_bunch = new object(); public static Barrier barrier = new Barrier(2); public static int FindBananas() { // Lock tree first, then banana lock (tree) { lock (banana_bunch) { Console.WriteLine("Got bananas."); return 0; } } } static void Gorilla_Start(object lockOrderObj) { Debugger.Break(); bool lockTreeFirst = (bool)lockOrderObj; Gorilla koko = new Gorilla(lockTreeFirst); int result = 0; var done = new ManualResetEventSlim(false); Thread t = new Thread(() => { result = koko.WakeUp(); done.Set(); }); t.Start(); done.Wait(); } static void Main(string[] args) { List<Thread> threads = new List<Thread>(); // Start two threads with opposite lock orders threads.Add(new Thread(Gorilla_Start)); threads[0].Start(true); // First gorilla locks tree then banana threads.Add(new Thread(Gorilla_Start)); threads[1].Start(false); // Second gorilla locks banana then tree foreach (var t in threads) { t.Join(); } } } class Gorilla { private readonly bool lockTreeFirst; public Gorilla(bool lockTreeFirst) { this.lockTreeFirst = lockTreeFirst; } public int WakeUp() { int myResult = MorningWalk(); return myResult; } public int MorningWalk() { Debugger.Break(); if (lockTreeFirst) { lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } else { lock (Jungle.banana_bunch) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } return 0; } public void GobbleUpBananas() { Console.WriteLine("Trying to gobble up food..."); DoSomeMonkeyBusiness(); } public void DoSomeMonkeyBusiness() { Thread.Sleep(1000); Console.WriteLine("Monkey business done"); } } }Wählen Sie im Menü "Datei " die Option "Alle speichern" aus.
Wählen Sie im Menü Erstellen die Option Lösung erstellen.
Verwenden Sie die Threads-Ansicht des Fensters "Parallele Stacks"
So starten Sie das Debuggen:
Wählen Sie im Menü " Debuggen " die Option "Debuggen starten " (oder "F5") aus, und warten Sie, bis der erste
Debugger.Break()Treffer erfolgt.Note
In C++ hält der Debugger bei
__debug_break()an. Die restlichen Codeverweise und Abbildungen in diesem Artikel gelten für die C#-Version, aber die gleichen Debugprinzipien gelten für C++.Drücken Sie einmal F5 , und der Debugger wird wieder in derselben
Debugger.Break()Zeile angehalten.Dies pausiert den zweiten Aufruf von
Gorilla_Start, der innerhalb eines zweiten Threads erfolgt.Tip
Der Debugger greift auf per-Thread-Ebene in den Code ein. Das bedeutet zum Beispiel, dass die App möglicherweise in den Code eines anderen Threads einbricht, wenn Sie F5 drücken, um die Ausführung fortzusetzen, und die App den nächsten Haltepunkt erreicht. Wenn Sie dieses Verhalten für Debuggingzwecke verwalten müssen, können Sie zusätzliche Haltepunkte, bedingte Haltepunkte hinzufügen oder "Alle aufheben" verwenden. Weitere Informationen zur Verwendung von bedingten Haltepunkten finden Sie unter Folgen eines einzelnen Threads mit bedingten Haltepunkten.
Wählen Sie " > Windows > Parallel-Stapel debuggen" aus, um das Fenster "Parallele Stapel" zu öffnen, und wählen Sie dann " Threads " aus der Dropdownliste "Ansicht" im Fenster aus.
In der Ansicht Threads werden der Stapelrahmen und der Aufrufpfad des aktuellen Threads blau hervorgehoben. Die aktuelle Position des Threads wird durch den gelben Pfeil angezeigt.
Beachten Sie, dass die Bezeichnung für den Aufrufstapel
Gorilla_Start2 Threads ist. Wenn Sie zuletzt F5 gedrückt haben, haben Sie einen anderen Thread gestartet. Zur Vereinfachung in komplexen Apps werden identische Aufrufstapel in einer einzigen visuellen Darstellung gruppiert. Dies vereinfacht potenziell komplexe Informationen, insbesondere in Szenarien mit vielen Threads.Während des Debuggens können Sie umschalten, ob externer Code angezeigt wird. Wenn Sie das Feature umschalten möchten, aktivieren oder deaktivieren Sie Externer Code anzeigen. Wenn Sie externen Code anzeigen, können Sie diese exemplarische Vorgehensweise weiterhin verwenden, ihre Ergebnisse können sich jedoch von den Abbildungen unterscheiden.
Wenn Sie F5 erneut drücken, wird der Debugger in der Zeile
Debugger.Break()der MethodeMorningWalkpausiert.Das Fenster "Parallele Stapel" zeigt die Position des aktuellen ausgeführten Threads in der
MorningWalkMethode an.
Zeigen Sie mit der Maus auf die
MorningWalkMethode, um Informationen zu den beiden Threads abzurufen, die durch den gruppierten Aufrufstapel dargestellt werden.Der aktuelle Thread wird auch in der Threadliste in der Debugsymbolleiste angezeigt.
Sie können die Threadliste verwenden, um den Debuggerkontext in einen anderen Thread zu wechseln. Dadurch wird nicht der aktuell ausgeführte Thread geändert, sondern nur der Debuggerkontext.
Alternativ können Sie den Debuggerkontext wechseln, indem Sie in der Threads-Ansicht auf eine Methode doppelklicken oder mit der rechten Maustaste auf eine Methode in der Threads-Ansicht klicken und "Zu Frame>wechseln[Thread-ID]" auswählen.
Drücken Sie erneut F5 , und der Debugger hält in der
MorningWalkMethode für den zweiten Thread an.
Je nach Ausführungszeitpunkt der Threadausführung sehen Sie zu diesem Zeitpunkt entweder separate oder gruppierte Aufrufstapel.
In der vorherigen Abbildung sind die Aufrufstapel für die beiden Threads teilweise gruppiert. Die identischen Segmente der Aufrufstapel sind gruppiert, und Pfeillinien zeigen auf die Segmente, die getrennt sind (d. a. nicht identisch). Der aktuelle Stapelrahmen wird durch die blaue Hervorhebung angezeigt.
Drücken Sie erneut F5 , und Es wird eine lange Verzögerung angezeigt, und die Threads-Ansicht zeigt keine Informationen zum Aufrufstapel an.
Die Verzögerung wird durch einen Deadlock verursacht. In der Threads-Ansicht wird nichts angezeigt. Auch wenn die Threads möglicherweise von Ihnen blockiert wurden, sind sie im Debugger derzeit nicht pausiert.
Note
In C++ wird auch eine Fehlermeldung angezeigt, die darauf hinweist, dass
abort()aufgerufen wurde.Tip
Die Schaltfläche Alle unterbrechen ist eine gute Möglichkeit, um Aufrufstapelinformationen abzurufen, wenn ein Deadlock auftritt oder alle Threads aktuell blockiert sind.
Wählen Sie am oberen Rand der IDE in der Debug-Symbolleiste die Schaltfläche Alle unterbrechen (Pausensymbol) aus, oder verwenden Sie STRG+ALT+UMBRUCH.
Der obere Rand des Aufrufstapels in der Threads-Ansicht zeigt an, dass
FindBananasfestgefahren ist. Der Ausführungspunkt inFindBananasist ein geschweifter grüner Pfeil, der den aktuellen Debuggerkontext angibt, aber es teilt uns mit, dass die Threads derzeit nicht ausgeführt werden.Note
In C++ sind die hilfreichen Informationen und Symbole zu einer Deadlock-Erkennung nicht sichtbar. Sie entdecken jedoch nach wie vor den gekrümmten grünen Pfeil in
Jungle.FindBananas, der auf den Standort des Deadlocks hinweist.Im Code-Editor finden wir den geschweiften grünen Pfeil in der
lockFunktion. Die beiden Threads werden in der Funktionlockin der MethodeFindBananasblockiert.Je nach Reihenfolge der Threadausführung, erscheint der Deadlock entweder in der
lock(tree)-Anweisung oder in derlock(banana_bunch)-Anweisung.Der Aufruf von
lockblockiert die Threads in derFindBananasMethode. Ein Thread wartet darauf, dass die Sperretreevom anderen Thread freigegeben wird, der andere Thread wartet jedoch darauf, dass die Sperrebanana_bunchlosgelassen wird, bevor sie die Sperretreeloslassen kann. Dies ist ein Beispiel für einen klassischen Deadlock, der auftritt, wenn zwei Threads aufeinander warten.Wenn Sie Copilot verwenden, können Sie auch KI-generierte Threadzusammenfassungen abrufen, um potenzielle Deadlocks zu identifizieren.
Reparieren Sie den Beispielcode
Um diesen Code zu beheben, erwerben Sie immer mehrere Sperren in einer konsistenten, globalen Reihenfolge über alle Threads hinweg. Dies verhindert kreisförmige Wartezeiten und beseitigt Deadlocks.
Um das Deadlock zu beheben, ersetzen Sie den Code in
MorningWalkdurch den folgenden Code.public int MorningWalk() { Debugger.Break(); // Always lock tree first, then banana_bunch lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // OK to remove lock (Jungle.banana_bunch) { Jungle.FindBananas(); GobbleUpBananas(); } } return 0; }Starten Sie die App neu.
Summary
In dieser Schritt-für-Schritt-Anleitung wurde das Parallel-Stacks-Debuggerfenster veranschaulicht. Verwenden Sie dieses Fenster für echte Projekte, die Multithread-Code verwenden. Sie können parallelen Code untersuchen, der in C++, C# oder Visual Basic geschrieben wurde.