Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V tomto kurzu se dozvíte, jak pomocí zobrazení Úlohy okna Paralelní zásobníky ladit asynchronní aplikaci jazyka C#. Toto okno vám pomůže pochopit a ověřit chování kódu za běhu, který používá vzor async/await, označovaný také jako asynchronní vzor založený na úlohách (TAP).
Pro aplikace používající knihovnu TPL (Task Parallel Library), ale ne vzor asynchronní/await nebo pro aplikace jazyka C++ pomocí modulu Concurrency Runtime, použijte k ladění zobrazení Vlákna v okně Paralelní zásobníky. Další informace naleznete v tématu Ladění zablokování a zobrazení vláken a úloh v okně Paralelní zásobníky.
Zobrazení úkolů vám pomůže:
Zobrazte vizualizace zásobníku volání pro aplikace, které používají vzor async/await. V těchto scénářích poskytuje zobrazení Úkoly ucelenější přehled o stavu vaší aplikace.
Identifikujte asynchronní kód, který je naplánovaný ke spuštění, ale ještě není spuštěný. Například požadavek HTTP, který nevrátil žádná data, se pravděpodobně zobrazí v zobrazení Úlohy místo zobrazení Vlákna, což vám pomůže izolovat problém.
Pomoc s identifikací problémů, jako je asynchronní vzor synchronizace, spolu s radami souvisejícími s potenciálními problémy, jako jsou blokované nebo čekající úlohy. Vzor sync-over-async kódu odkazuje na kód, který volá asynchronní metody synchronním způsobem, což je známo, že blokuje vlákna a je nejčastější příčinou vyčerpání vláken v poolu.
Asynchronní zásobníky volání
Pohled Úlohy v Paralelních zásobnících poskytuje vizualizaci pro asynchronní zásobníky volání, aby bylo vidět, co se děje (nebo co se má stát) v aplikaci.
Tady je několik důležitých bodů, které je potřeba pamatovat při interpretaci dat v zobrazení Úkoly.
Asynchronní zásobníky volání jsou logické nebo virtuální zásobníky volání, nikoli zásobníky fyzických volání představujících zásobník. Při práci s asynchronním kódem (například pomocí klíčového
awaitslova) ladicí program poskytuje zobrazení "zásobníků asynchronních volání" nebo "zásobníků virtuálních volání". Asynchronní zásobníky volání se liší od zásobníků volání založených na vláknech nebo "fyzických zásobníků", protože asynchronní zásobníky volání nemusí nutně běžet v žádném fyzickém vlákně. Místo toho jsou asynchronní zásobníky volání pokračování nebo "přísliby" kódu, které se budou spouštět v budoucnu asynchronně. Zásobníky volání se vytvářejí pomocí kontinuací.Asynchronní kód, který je naplánovaný, ale aktuálně není spuštěný, se nezobrazuje ve fyzickém zásobníku volání, ale měl by se zobrazit v asynchronním zásobníku volání v zobrazení Úkoly. Pokud blokujete vlákna pomocí metod, jako například
.Waitnebo.Result, může se místo toho zobrazit kód v zásobníku fyzických volání.Asynchronní virtuální zásobníky volání nejsou vždy intuitivní kvůli větvení, které vzniká použitím volání metod, jako jsou
.WaitAnynebo.WaitAll.Okno Zásobník volání může být užitečné v kombinaci se zobrazením Úlohy, protože zobrazuje zásobník fyzických volání pro aktuální spuštěné vlákno.
Identické části zásobníku virtuálních volání jsou seskupené, aby se zjednodušila vizualizace složitých aplikací.
Následující koncepční animace ukazuje, jak se seskupování používá na zásobníky virtuálních volání. Seskupují se pouze identické segmenty virtuálního zásobníku volání. Najeďte myší na seskupený zásobník volání a zajistěte idenitfy vláken, na kterých jsou spuštěné úlohy.
Ukázka v jazyce C#
Vzorový kód v tomto návodu je určen pro aplikaci, která simuluje den v životě gorily. Účelem tohoto cvičení je pochopit, jak používat zobrazení Úkoly v okně Paralelní zásobníky k ladění asynchronních aplikací.
Ukázka obsahuje příklad použití antipatternu sync-over-async, což může vést k hladovění fondu vláken.
Aby byl zásobník volání intuitivní, ukázková aplikace provede následující postupné kroky:
- Vytvoří objekt představující gorilu.
- Gorilla se vzbudí.
- Gorilla chodí ráno.
- Gorilla najde banány v džungli.
- Gorilla jí.
- Gorilla se zabývá opičím obchodem.
Vytvoření ukázkového projektu
Otevřete Visual Studio a vytvořte nový projekt.
Pokud úvodní okno není otevřené, zvolte Soubor>Okno Start.
V úvodním okně zvolte Nový projekt.
V okně Vytvořit nový projekt zadejte konzolu do vyhledávacího pole. Potom v seznamu Jazyků zvolte jazyka C# a pak v seznamu Platformy zvolte Windows.
Po použití filtrů jazyka a platformy zvolte konzolovou aplikaci pro .NET a pak zvolte Další.
Poznámka:
Pokud nevidíte správnou šablonu, přejděte na NástrojeZískat nástroje >a funkce..., čímž se otevře instalační program sady Visual Studio. Zvolte úlohu vývoje desktopových aplikací .NET a pak zvolte Upravit.
V okně Konfigurovat nový projekt zadejte název nebo do pole Název projektu použijte výchozí název. Pak zvolte Další.
Pro .NET zvolte doporučenou cílovou architekturu nebo .NET 8 a pak zvolte Vytvořit.
Zobrazí se nový konzolový projekt. Po vytvoření projektu se zobrazí zdrojový soubor.
Otevřete soubor kódu .cs v projektu. Odstraňte jeho obsah a vytvořte prázdný soubor kódu.
Do prázdného souboru kódu vložte následující kód pro vybraný jazyk.
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"); } } }Po aktualizaci souboru kódu uložte změny a sestavte řešení.
V nabídce Soubor vyberte Uložit vše.
V nabídce Sestavení vyberte Sestavit řešení.
Použijte zobrazení úloh v okně Paralelní zásobníky
V nabídce Ladění vyberte Spustit ladění (nebo F5) a počkejte, až se spustí první
Debugger.Break().Stiskněte jednou klávesu F5 a ladicí program se znovu pozastaví na stejném
Debugger.Break()řádku.Tím se pozastaví druhé volání
Gorilla_Start, které se vyskytuje v rámci druhé asynchronní úlohy.Výběrem možnosti Ladit > paralelní zásobníky systému Windows > otevřete okno Paralelní zásobníky a pak v rozevíracím seznamu Zobrazení vyberte Úkoly.
Všimněte si popisků asynchronních zásobníků volání, které označují 2 asynchronní logické zásobníky. Když jste naposledy stiskli klávesu F5, spustili jste další úkol. Pro zjednodušení v složitých aplikacích jsou identické asynchronní zásobníky volání seskupené do jedné vizuální reprezentace. To poskytuje podrobnější informace, zejména ve scénářích s mnoha úlohami.
Na rozdíl od zobrazení Úlohy se v okně Zásobník volání zobrazuje zásobník volání pouze pro aktuální vlákno, nikoli pro více úloh. Často je užitečné zobrazit oba společně, abyste viděli úplnější přehled o stavu aplikace.
Návod
V okně Zásobník volání můžete zobrazit informace, jako je zablokování, pomocí popisu
Async cycle.Během ladění můžete přepnout, jestli se zobrazí externí kód. Pokud chcete tuto funkci přepnout, klikněte pravým tlačítkem myši na záhlaví tabulky Název okna Zásobník volání a pak vyberte nebo zrušte zaškrtnutí políčka Zobrazit externí kód. Pokud zobrazíte externí kód, můžete tento názorný postup použít, ale výsledky se můžou lišit od ilustrací.
Stiskněte znovu klávesu F5 a ladicí program se v
DoSomeMonkeyBusinessmetodě pozastaví.
Toto zobrazení ukazuje ucelenější asynchronní zásobník volání po přidání více asynchronních metod do interního řetězce pokračování, ke kterému dochází při použití
awaita podobných metodách.DoSomeMonkeyBusinessmůže nebo nemusí být přítomna v horní části zásobníku asynchronních volání, protože se jedná o asynchronní metodu, ale ještě nebyla přidána do řetězce pokračování. V následujících krocích prozkoumáme, proč tomu tak je.Toto zobrazení také zobrazuje zablokovanou ikonu pro
Jungle.Main
. To je informativní, ale obvykle neznamená problém. Blokovaný úkol je blokovaný, protože čeká na dokončení jiného úkolu, událost, která se má signalizovat nebo uvolnit zámek.Najeďte myší na metodu
GobbleUpBananasa získejte informace o dvou vláknech, ve kterých jsou spuštěné úlohy.
Aktuální vlákno se také zobrazí v seznamu Vlákno na panelu nástrojů Ladění.
Pomocí seznamu vláken můžete přepnout kontext ladicího programu na jiné vlákno.
Znovu stiskněte F5 a ladicí program se pozastaví v metodě
DoSomeMonkeyBusinesspro druhý úkol.
V závislosti na načasování spuštění úlohy se v tomto okamžiku zobrazí buď samostatné nebo seskupené asynchronní zásobníky volání.
Na předchozím obrázku jsou asynchronní zásobníky volání pro tyto dva úkoly oddělené, protože nejsou identické.
Znovu stiskněte klávesu F5 a zobrazí se dlouhá prodleva a zobrazení Úkoly nezobrazuje žádné informace o zásobníku asynchronních volání.
Zpoždění je způsobeno dlouhotrvající úlohou. Pro účely tohoto příkladu simuluje dlouhotrvající úlohu, jako je například webový požadavek, což může vést k vyčerpání zdrojů fondu vláken. V zobrazení Úkoly se nic nezobrazuje, protože i když úkoly mohou být blokovány, vy sami nyní v ladicím programu pozastaveni nejste.
Návod
Tlačítko Přerušit vše je dobrým způsobem, jak získat informace o zásobníku volání, pokud dojde k zablokování nebo jsou aktuálně blokované všechny úlohy a vlákna.
V horní části integrovaného vývojového prostředí na ladicím panelu nástrojů vyberte tlačítko Přerušit vše (ikona pauzy), Ctrl + Alt + Break.
V horní části zásobníku asynchronních volání v zobrazení Úkoly vidíte, že
GobbleUpBananasje blokovaný. Ve skutečnosti jsou ve stejném okamžiku zablokované dva úkoly. Blokovaný úkol nemusí být nutně neočekávaný a nemusí nutně znamenat problém. Pozorované zpoždění v provádění však značí problém a zásobník volání zde poskytuje informace o tom, kde se problém nachází.Na levé straně předchozího snímku obrazovky označuje zvlněná zelená šipka aktuální kontext ladicího programu. V metodě
mb.Wait()jsou dva úkoly zablokovány naGobbleUpBananas.Okno Zásobník volání také ukazuje, že aktuální vlákno je blokováno.
Volání
Wait()blokuje vlákna v rámci synchronního voláníGobbleUpBananas. Toto je příklad antipatternu sync-over-async, a pokud by k tomu došlo ve vlákně uživatelského rozhraní nebo během náročného zpracování, obvykle by se to řešilo opravou kódu pomocíawait. Další informace naleznete v tématu Odstraňování chyb hladovění fondu vláken. Chcete-li použít nástroje profilování k ladění hladovění fondu vláken, projděte si případovou studii: Izolace problému s výkonem.Rovněž je zajímavé, že se
DoSomeMonkeyBusinessnezobrazuje v zásobníku volání. Aktuálně je naplánovaná, neběží, takže se zobrazí pouze v asynchronním zásobníku volání v zobrazení Úkoly.Návod
Ladicí program se rozdělí na kód na základě jednotlivých vláken. To například znamená, že pokud stisknete F5 pro pokračování v provádění a aplikace narazí na další bod přerušení, může se zastavit v kódu na jiném vlákně. Pokud to potřebujete spravovat pro účely ladění, můžete přidat další zarážky, přidat podmíněné zarážky nebo použít funkci Přerušit vše. Další informace o tomto chování najdete v části Sledování jednoho vlákna s podmíněnými zarážkami.
Oprava vzorového kódu
Nahraďte metodu
GobbleUpBananasnásledujícím kódem.public async Task GobbleUpBananas(int food) // Previously returned void. { Console.WriteLine("Trying to gobble up food..."); //Task mb = DoSomeMonkeyBusiness(); //mb.Wait(); await DoSomeMonkeyBusiness(); }V metodě
MorningWalkvolejte GobbleUpBananas pomocíawait.await GobbleUpBananas(myResult);Vyberte tlačítko Restartovat (Ctrl + Shift + F5) a několikrát stiskněte klávesu F5, dokud se aplikace nezdá, že se zamrzne.
Stiskněte Přerušit vše.
Tentokrát se
GobbleUpBananasspouští asynchronně. Když přerušíte, zobrazí se asynchronní zásobník volání.
Okno Zásobník volání je prázdné s výjimkou
ExternalCodepoložky.Editor kódu nám nic nezobrazuje, s výjimkou zprávy, která indikuje, že všechna vlákna spouští externí kód.
Zobrazení Úkolů ale poskytuje užitečné informace.
DoSomeMonkeyBusinessje podle očekávání v horní části zásobníku asynchronních volání. To nám správně říká, kde se nachází dlouhotrvající metoda. Je užitečné izolovat problémy s asynchronním čekáním (async/await) v případech, kdy fyzický zásobník volání v okně Zásobník volání neposkytuje dostatek podrobností.
Shrnutí
Tento názorný postup ukázal ladicí okno Paralelní zásobníky. Toto okno použijte u aplikací, které používají vzor async/await.