Sdílet prostřednictvím


Ladění asynchronní aplikace

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 await slova) 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 .Wait nebo .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 .WaitAny nebo .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.

    Obrázek seskupení zásobníků virtuálních volání

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:

  1. Vytvoří objekt představující gorilu.
  2. Gorilla se vzbudí.
  3. Gorilla chodí ráno.
  4. Gorilla najde banány v džungli.
  5. Gorilla jí.
  6. Gorilla se zabývá opičím obchodem.

Vytvoření ukázkového projektu

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

  2. Otevřete soubor kódu .cs v projektu. Odstraňte jeho obsah a vytvořte prázdný soubor kódu.

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

  4. V nabídce Soubor vyberte Uložit vše.

  5. V nabídce Sestavení vyberte Sestavit řešení.

Použijte zobrazení úloh v okně Paralelní zásobníky

  1. V nabídce Ladění vyberte Spustit ladění (nebo F5) a počkejte, až se spustí první Debugger.Break() .

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

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

    Snímek obrazovky zobrazení úkolů v okně Paralelní zásobníky

    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.

    Snímek zásobníku volání

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

  4. Stiskněte znovu klávesu F5 a ladicí program se v DoSomeMonkeyBusiness metodě pozastaví.

    Snímek obrazovky zobrazení úkolů po stisknutí klávesy F5.

    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í await a podobných metodách. DoSomeMonkeyBusiness můž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.Mainblokovaný stav. 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.

  5. Najeďte myší na metodu GobbleUpBananas a získejte informace o dvou vláknech, ve kterých jsou spuštěné úlohy.

    Snímek obrazovky s vlákny přidruženými ke zásobníku volání

    Aktuální vlákno se také zobrazí v seznamu Vlákno na panelu nástrojů Ladění.

    Snímek obrazovky s aktuálním vláknem na panelu nástrojů Ladění

    Pomocí seznamu vláken můžete přepnout kontext ladicího programu na jiné vlákno.

  6. Znovu stiskněte F5 a ladicí program se pozastaví v metodě DoSomeMonkeyBusiness pro druhý úkol.

    Snímek obrazovky se zobrazením Úkolů za sekundou F5

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

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

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

    Snímek obrazovky se zobrazením Úkoly po výběru možnosti Přerušit vše

    V horní části zásobníku asynchronních volání v zobrazení Úkoly vidíte, že GobbleUpBananas je 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 na GobbleUpBananas.

    Okno Zásobník volání také ukazuje, že aktuální vlákno je blokováno.

    Snímek obrazovky zásobníku volání po výběru možnosti Přerušit vše

    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 DoSomeMonkeyBusiness nezobrazuje 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

  1. Nahraďte metodu GobbleUpBananas ná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();
     }
    
  2. V metodě MorningWalk volejte GobbleUpBananas pomocí await.

    await GobbleUpBananas(myResult);
    
  3. Vyberte tlačítko Restartovat (Ctrl + Shift + F5) a několikrát stiskněte klávesu F5, dokud se aplikace nezdá, že se zamrzne.

  4. Stiskněte Přerušit vše.

    Tentokrát se GobbleUpBananas spouští asynchronně. Když přerušíte, zobrazí se asynchronní zásobník volání.

    Snímek obrazovky s kontextem ladicího programu po opravě kódu

    Okno Zásobník volání je prázdné s výjimkou ExternalCode polož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. DoSomeMonkeyBusiness je 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.