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 Lernprogramm wird gezeigt, wie Sie die Aufgabenansicht des Fensters "Parallele Stapel" verwenden, um eine Asynchrone C#-Anwendung zu debuggen. Dieses Fenster hilft Ihnen, das Laufzeitverhalten von Code zu verstehen und zu überprüfen, der das asynchrone/await-Muster verwendet, auch als aufgabenbasiertes asynchrones Muster (TAP) bezeichnet.
Verwenden Sie für Apps, die die Task Parallel Library (TPL) verwenden, jedoch nicht das asynchrone/await-Muster oder für C++-Apps, die die Parallelitätslaufzeit verwenden, die Threads-Ansicht im Fenster "Parallele Stapel" zum Debuggen. Weitere Informationen finden Sie unter Debuggen eines Deadlocks und Anzeigen von Threads und Aufgaben im Fenster "Parallele Stapel".
Die Ansicht "Aufgaben" hilft Ihnen bei folgenden Aufgaben:
Zeigen Sie Aufrufstapelvisualisierungen für Apps an, die das asynchrone/await-Muster verwenden. In diesen Szenarien bietet die Ansicht "Aufgaben" ein vollständiges Bild des App-Zustands.
Identifizieren Sie asynchronen Code, der für die Ausführung geplant ist, aber noch nicht ausgeführt wird. Beispielsweise wird eine HTTP-Anforderung, die keine Daten zurückgegeben hat, in der Aufgabenansicht anstelle der Threads-Ansicht angezeigt, wodurch Sie das Problem isolieren können.
Helfen Sie beim Identifizieren von Problemen wie dem Sync-over-Async-Pattern sowie von Hinweisen auf potenzielle Probleme wie blockierte oder wartende Aufgaben. Das Sync-over-Async-Codemuster bezieht sich auf Code, der asynchrone Methoden auf synchrone Weise aufruft, was dazu bekannt ist, Threads zu blockieren und die häufigste Ursache für das Verhungern des Thread-Pools darstellt.
Asynchrone Aufrufstapel
Die Aufgabenansicht in parallelen Stapeln bietet eine Visualisierung für asynchrone Aufrufstapel, sodass Sie sehen können, was in Ihrer Anwendung passiert (oder was passieren soll).
Hier sind einige wichtige Punkte, die Sie beim Interpretieren von Daten in der Ansicht "Aufgaben" beachten sollten.
Asynchrone Aufrufstapel sind logische oder virtuelle Aufrufstapel und keine physischen Aufrufstapel, die den Stapel darstellen. Beim Arbeiten mit asynchronem Code (z. B. mithilfe des
awaitSchlüsselworts) stellt der Debugger eine Ansicht der "asynchronen Aufrufstapel" oder "virtuelle Aufrufstapel" bereit. Asynchrone Aufrufstapel unterscheiden sich von threadbasierten Aufrufstapeln oder "physischen Stapeln", da asynchrone Aufrufstapel derzeit nicht unbedingt in einem physischen Thread ausgeführt werden. Stattdessen handelt es sich bei den asynchronen Aufrufstapeln um Fortsetzungen oder "Zusagen" von Code, die in Zukunft asynchron ausgeführt werden. Die Aufrufstapel werden mithilfe von Fortsetzungen erstellt.Asynchroner Code, der geplant, aber derzeit nicht ausgeführt wird, wird nicht im physischen Aufrufstapel angezeigt, sollte aber im asynchronen Aufrufstapel in der Aufgabenansicht angezeigt werden. Wenn Sie Threads mit Methoden wie
.Waitoder.Resultblockieren, wird stattdessen der Code im physischen Aufrufstapel angezeigt.Asynchrone virtuelle Aufrufstapel sind nicht immer intuitiv, da sie aufgrund des Verzweigens entstehen, das durch den Einsatz von Methodenaufrufen wie
.WaitAnyoder.WaitAllverursacht wird.Das Fenster "Aufrufstapel " kann in Kombination mit der Ansicht "Aufgaben" nützlich sein, da der physische Aufrufstapel für den aktuellen ausgeführten Thread angezeigt wird.
Identische Abschnitte des virtuellen Aufrufstapels werden gruppiert, um die Visualisierung für komplexe Apps zu vereinfachen.
Die folgende konzeptionelle Animation zeigt, wie die Gruppierung auf virtuelle Aufrufstapel angewendet wird. Es werden nur identische Segmente eines virtuellen Anrufstapels gruppiert. Fahren Sie mit der Maus über einen gruppierten Aufrufstapel, um die Threads zu identifizieren, die die Aufgaben ausführen.
C#-Beispiel
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 Aufgabenansicht des Fensters "Parallele Stapel" verwenden, um eine asynchrone Anwendung zu debuggen.
Das Beispiel enthält ein Beispiel für die Verwendung des Sync-over-Async-Antipatterns, das zu einem Aushungern des Threadpools führen kann.
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
Ö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#- aus der Liste "Sprache" aus, und wählen Sie dann in der Liste "Plattform" Windows aus.
Nachdem Sie die Sprach- und Plattformfilter angewendet haben, wählen Sie die Konsolen-App für .NET und dann "Weiter" aus.
Hinweis
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 .NET 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 .cs Codedatei 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 AsyncTasks_SyncOverAsync { class Jungle { public static async Task<int> FindBananas() { await Task.Delay(1000); Console.WriteLine("Got bananas."); return 0; } static async Task Gorilla_Start() { Debugger.Break(); Gorilla koko = new Gorilla(); int result = await Task.Run(koko.WakeUp); } static async Task Main(string[] args) { List<Task> tasks = new List<Task>(); for (int i = 0; i < 2; i++) { Task task = Gorilla_Start(); tasks.Add(task); } await Task.WhenAll(tasks); } } class Gorilla { public async Task<int> WakeUp() { int myResult = await MorningWalk(); return myResult; } public async Task<int> MorningWalk() { int myResult = await Jungle.FindBananas(); GobbleUpBananas(myResult); return myResult; } /// <summary> /// Calls a .Wait. /// </summary> public void GobbleUpBananas(int food) { Console.WriteLine("Trying to gobble up food synchronously..."); Task mb = DoSomeMonkeyBusiness(); mb.Wait(); } public async Task DoSomeMonkeyBusiness() { Debugger.Break(); while (!System.Diagnostics.Debugger.IsAttached) { Thread.Sleep(100); } await Task.Delay(30000); Console.WriteLine("Monkey business done"); } } }Nachdem Sie die Codedatei aktualisiert haben, speichern Sie Die Änderungen, und erstellen Sie die Lösung.
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 Aufgabenansicht des Fensters "Parallele Stapel"
Wählen Sie im Menü " Debuggen " die Option "Debuggen starten " (oder "F5") aus, und warten Sie, bis der erste
Debugger.Break()Treffer erfolgt.Drücken Sie einmal F5 , und der Debugger wird wieder in derselben
Debugger.Break()Zeile angehalten.Dies pausiert im zweiten Aufruf des
Gorilla_Startdie innerhalb einer zweiten asynchronen Aufgabe stattfindet.Wählen Sie Debug > Windows > Parallel-Stapel aus, um das Fenster "Parallele Stapel" zu öffnen, und wählen Sie dann Aufgaben aus der Dropdown-Liste Ansicht im Fenster aus.
Beachten Sie, dass die Bezeichnungen für die asynchronen Aufrufstapel 2 Asynchrone logische Stapel beschreiben. Wenn Sie zuletzt F5 gedrückt haben, haben Sie eine weitere Aufgabe gestartet. Zur Vereinfachung in komplexen Apps werden identische asynchrone Aufrufstapel in einer einzigen visuellen Darstellung gruppiert. Dies bietet umfassendere Informationen, insbesondere in Szenarien mit vielen Aufgaben.
Im Gegensatz zur Ansicht "Aufgaben" zeigt das Fenster "Aufrufstapel " den Aufrufstapel nur für den aktuellen Thread an, nicht für mehrere Aufgaben. Es ist häufig hilfreich, beide zusammen anzuzeigen, um ein vollständiges Bild des App-Zustands anzuzeigen.
Tipp
Das Stapel aufrufen-Fenster kann Ihnen Informationen wie z. B. einen Deadlock anzeigen, indem Sie die Beschreibung
Async cycle.Während des Debuggens können Sie umschalten, ob externer Code angezeigt wird. Wenn Sie das Feature umschalten möchten, klicken Sie mit der rechten Maustaste auf die Kopfzeile der Namenstabelle des Fensters "Anrufliste ", und aktivieren oder deaktivieren Sie dann " 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.
Drücken Sie erneut F5 , und der Debugger wird in der
DoSomeMonkeyBusinessMethode angehalten.
Diese Ansicht zeigt einen vollständigen asynchronen Aufrufstapel, nachdem der internen Fortsetzungskette weitere asynchrone Methoden hinzugefügt wurden, was passiert, wenn
awaitund ähnliche Methoden verwendet werden.DoSomeMonkeyBusinessist möglicherweise oder möglicherweise nicht oben im asynchronen Aufrufstapel vorhanden, da es sich um eine asynchrone Methode handelt, die jedoch noch nicht zur Fortsetzungskette hinzugefügt wurde. Wir werden untersuchen, warum dies in den folgenden Schritten der Fall ist.In dieser Ansicht wird auch das Symbol "Blockiert" für
Jungle.Main
. Dies ist informativ, weist jedoch in der Regel nicht auf ein Problem hin. Eine blockierte Aufgabe ist eine, die blockiert wird, weil sie auf eine andere Aufgabe wartet, bis sie abgeschlossen ist, ein Ereignis, das signalisiert wird oder eine Sperre losgelassen wird.Zeigen Sie mit der Maus auf die
GobbleUpBananasMethode, um Informationen zu den beiden Threads abzurufen, die die Aufgaben ausführen.
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.
Drücken Sie erneut F5 , und der Debugger hält in der
DoSomeMonkeyBusinessMethode für den zweiten Vorgang an.
Je nach Zeitpunkt der Aufgabenausführung werden an diesem Punkt entweder separate oder gruppierte asynchrone Aufrufstapel angezeigt.
In der vorherigen Abbildung sind die asynchronen Aufrufstapel für die beiden Aufgaben getrennt, da sie nicht identisch sind.
Drücken Sie erneut F5 , und es wird eine lange Verzögerung angezeigt, und in der Aufgabenansicht werden keine asynchronen Aufrufstapelinformationen angezeigt.
Die Verzögerung wird durch eine lang andauernde Aufgabe verursacht. In diesem Beispiel wird eine lang andauernde Aufgabe wie eine Webanforderung simuliert, die zu einem Threadpool-Verhungern führen könnte. In der Aufgabenansicht wird nichts angezeigt, denn obwohl Aufgaben blockiert sein können, sind Sie im Debugger derzeit nicht angehalten.
Tipp
Die Alles abbrechen Die Schaltfläche Aufrufen ist eine gute Möglichkeit, um Informationen über den Aufrufstapel zu erhalten, wenn eine Blockierung auftritt oder alle Aufgaben und Threads derzeit blockiert sind.
Wählen Sie oben in der IDE in der Fehlersuche-Symbolleiste die Option Alles abbrechen Schaltfläche (Pausensymbol), Strg + Alt + Pause.
Am oberen Rand des asynchronen Aufrufstapels in der Aufgabenansicht sehen Sie, dass
GobbleUpBananasblockiert ist. Tatsächlich werden zwei Vorgänge an demselben Punkt blockiert. Eine blockierte Aufgabe ist nicht unbedingt unerwartet und bedeutet nicht unbedingt, dass ein Problem vorliegt. Die beobachtete Verzögerung bei der Ausführung weist jedoch auf ein Problem hin, und die Informationen zum Aufrufstapel hier zeigen den Speicherort des Problems an.Auf der linken Seite des vorherigen Screenshots zeigt der geschweifte grüne Pfeil den aktuellen Debuggerkontext an. Die beiden Aufgaben sind blockiert auf
mb.Wait()in denGobbleUpBananasMethode.Das Fenster "Aufrufstapel" zeigt auch an, dass der aktuelle Thread blockiert ist.
Der Aufruf von
Wait()blockiert die Threads innerhalb des synchronen Aufrufs vonGobbleUpBananas. Dies ist ein Beispiel für das Sync-über-Async-Antipattern, und wenn dies in einem UI-Thread oder unter hoher Verarbeitungsbelastung aufgetreten ist, würde es in der Regel mit einer Codekorrekturawaitbehoben werden. Für weitere Informationen siehe Fehlersuche thread pool starvation. Um Werkzeuge zur Profilerstellung zu verwenden, um Thread-Pool-Starvation zu beheben, siehe Fallstudie: Isolieren eines Leistungsproblems.Auch interessant,
DoSomeMonkeyBusinesswird nicht auf dem Anrufstapel angezeigt. Die Transaktion ist derzeit geplant und nicht in Ausführung, so dass sie nur im asynchronen Aufrufstapel in der Aufgabenansicht angezeigt wird.Tipp
Der Debugger greift auf per-Thread-Ebene in den Code ein. Das bedeutet zum Beispiel, dass wenn Sie F5 um die Ausführung fortzusetzen, und die Anwendung den nächsten Haltepunkt erreicht, kann sie in den Code eines anderen Threads einbrechen. Wenn Sie dies für Debuggingzwecke verwalten müssen, können Sie zusätzliche Haltepunkte hinzufügen, bedingte Haltepunkte hinzufügen oder "Alle aufheben" verwenden. Weitere Informationen zu diesem Verhalten finden Sie unter Einen einzelnen Thread mit bedingten Haltepunkten nachverfolgen.
Reparieren Sie den Beispielcode
Ersetzen Sie die
GobbleUpBananas-Methode durch den folgenden Code.public async Task GobbleUpBananas(int food) // Previously returned void. { Console.WriteLine("Trying to gobble up food..."); //Task mb = DoSomeMonkeyBusiness(); //mb.Wait(); await DoSomeMonkeyBusiness(); }Rufen Sie in der
MorningWalkMethode GobbleUpBananas mithilfe vonawaitauf.await GobbleUpBananas(myResult);Wählen Sie die Schaltfläche " Neustart " aus (STRG+UMSCHALT+F5), und drücken Sie dann mehrmals F5, bis die App "hängen" angezeigt wird.
Drücken Sie Alles unterbrechen.
Dieses Mal
GobbleUpBananaswird asynchron ausgeführt. Beim Unterbrechen wird der asynchrone Aufrufstapel angezeigt.
Das Fenster "Anrufstapel" ist leer, mit Ausnahme des
ExternalCodeEintrags.Der Code-Editor zeigt uns nichts an, es sei denn, er gibt eine Meldung an, die angibt, dass alle Threads externen Code ausführen.
Die Ansicht "Aufgaben" bietet jedoch nützliche Informationen.
DoSomeMonkeyBusinessbefindet sich oben im asynchronen Aufrufstapel wie erwartet. Dadurch wird uns korrekt mitgeteilt, wo sich die langlaufende Methode befindet. Dies ist hilfreich, um Async/Wait-Probleme zu isolieren, wenn der physische Aufrufstapel im Aufrufstapel-Fenster nicht genügend Einzelheiten liefert.
Zusammenfassung
In dieser Schritt-für-Schritt-Anleitung wurde das Parallel-Stacks-Debuggerfenster veranschaulicht. Verwenden Sie dieses Fenster für Apps, die das asynchrone/await-Muster verwenden.