Dela via


Felsöka ett asynkront program

Den här handledningen visar hur du använder vyn Uppgifter i fönstret Parallella staplar för att felsöka en C#-asynkron applikation. Det här fönstret hjälper dig att förstå och verifiera körningsbeteendet för kod som använder mönstret async/await, även kallat det aktivitetsbaserade asynkrona mönstret (TAP).

För appar som använder TPL (Task Parallel Library) men inte mönstret async/await, eller för C++-appar som använder Samtidighetskörning, använder du vyn Trådar i fönstret Parallella staplar för felsökning. Mer information finns i Felsöka ett dödläge och Visa trådar och uppgifter i fönstret Parallella staplar.

Uppgiftsvyn hjälper dig att:

  • Visa anropsstackens visualiseringar för appar som använder mönstret async/await. I dessa scenarier ger vyn "Uppgifter" en mer komplett bild av applikationens tillstånd.

  • Identifiera asynkron kod som är schemalagd att köras men som ännu inte körs. En HTTP-begäran som inte har returnerat några data är till exempel mer sannolikt att visas i vyn Uppgifter i stället för vyn Trådar, vilket hjälper dig att isolera problemet.

  • Hjälp med att identifiera problem som sync-over-async-mönstret tillsammans med tips som rör potentiella problem, till exempel blockerade eller väntande uppgifter. Kodmönstret sync-over-async refererar till kod som anropar asynkrona metoder på ett synkront sätt, vilket är känt för att blockera trådar och är den vanligaste orsaken till utsvulten trådpool.

Asynkrona anropsstackar

Uppgiftsvyn i Parallella Staplar ger en visualisering av asynkrona anropsstackar, så att du kan se vad som händer (eller ska hända) i ditt program.

Här följer några viktiga punkter att komma ihåg när du tolkar data i vyn Uppgifter.

  • Asynkrona anropsstackar är logiska eller virtuella anropsstackar, inte fysiska anropsstackar som representerar stacken. När du arbetar med asynkron kod (till exempel med hjälp av nyckelordet await ) ger felsökningsprogrammet en vy över "asynkrona anropsstackar" eller "virtuella anropsstackar". Asynkrona anropsstackar skiljer sig från trådbaserade anropsstackar eller "fysiska staplar", eftersom asynkrona anropsstackar inte nödvändigtvis körs på någon fysisk tråd. I stället är asynkrona anropsstackar fortsättningar eller "löften" för kod som kommer att köras i framtiden, asynkront. Anropsstackarna skapas med fortsättningar.

  • Asynkron kod som är schemalagd men inte körs för närvarande visas inte i den fysiska anropsstacken, men bör visas i den asynkrona anropsstacken i Tasks-vyn. Om du blockerar trådar med metoder som .Wait eller .Resultkan du se koden i den fysiska anropsstacken i stället.

  • Asynkrona virtuella anropsstackar är inte alltid intuitiva på grund av förgrening som beror på användning av metodanrop som .WaitAny eller .WaitAll.

  • Fönstret Anropsstack kan vara användbart i kombination med vyn Arbetsuppgifter, eftersom det visar den fysiska anropsstacken för den aktuella körtråden.

  • Identiska delar av den virtuella anropsstacken grupperas tillsammans för att förenkla visualiseringen för komplexa appar.

    Följande konceptuella animering visar hur gruppering tillämpas på virtuella anropsstackar. Endast identiska segment i en virtuell anropsstack grupperas. Hovra över en grupperad anropsstack för att identifiera de trådar som utför uppgifterna.

    Bild av gruppering av virtuella anropsstackar.

C#-exempel

Exempelkoden i den här genomgången är för ett program som simulerar en dag i en gorillas liv. Syftet med övningen är att förstå hur du använder Uppgiftsvyn i fönstret Parallella staplar för att felsöka en asynkron applikation.

Exemplet innehåller ett exempel på hur du använder antimönstret sync-over-async, vilket kan resultera i utsvulten trådpool.

För att göra anropsstacken intuitiv utför exempelappen följande sekventiella steg:

  1. Skapar ett objekt som representerar en gorilla.
  2. Gorilla vaknar.
  3. Gorillan går på en morgonpromenad.
  4. Gorilla hittar bananer i djungeln.
  5. Gorilla äter.
  6. Gorilla sysslar med rackartyg.

Skapa exempelprojektet

  1. Öppna Visual Studio och skapa ett nytt projekt.

    Om startfönstret inte är öppet väljer du Fil>Startfönster.

    I startfönstret väljer du Nytt projekt.

    I fönstret Skapa ett nytt projekt anger eller skriver du konsolen i sökrutan. Välj sedan C# i listan Språk och välj sedan Windows i listan Plattform.

    När du har tillämpat språk- och plattformsfilter väljer du Konsolapp för .NET och sedan Nästa.

    Anmärkning

    Om du inte ser rätt mall går du till Verktyg>Hämta verktyg och funktioner... som öppnar Installationsprogrammet för Visual Studio. Välj arbetsbelastningen för .NET-skrivbordsutveckling och välj då Ändra.

    I fönstret Konfigurera det nya projektet skriver du ett namn eller använder standardnamnet i rutan Projektnamn . Välj sedan Nästa.

    För .NET väljer du antingen det rekommenderade målramverket eller .NET 8 och väljer sedan Skapa.

    Ett nytt konsolprojekt visas. När projektet har skapats visas en källfil.

  2. Öppna .cs kodfilen i projektet. Ta bort innehållet för att skapa en tom kodfil.

  3. Klistra in följande kod för det valda språket i den tomma kodfilen.

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

    När du har uppdaterat kodfilen sparar du ändringarna och skapar lösningen.

  4. På menyn Arkiv väljer du Spara alla.

  5. På menyn Build väljer du Build Solution.

Använd uppgiftsvyn i fönstret Parallella staplar

  1. På menyn Felsök väljer du Starta felsökning (eller F5) och väntar på att den första Debugger.Break() ska slås.

  2. Tryck på F5 en gång så pausar felsökningsprogrammet igen på samma Debugger.Break() rad.

    Detta pausar vid det andra anropet till Gorilla_Start, som inträffar inom en andra asynkron uppgift.

  3. Välj Felsöka > Windows > Parallel Stacks för att öppna fönstret Parallella staplar och välj sedan Uppgifter i listrutan Visa i fönstret.

    Skärmbild av vyn Tasks i fönstret Parallel Stacks.

    Observera att etiketterna för asynkrona anropsstackar beskriver 2 asynkrona logiska staplar. När du senast tryckte på F5 startade du en annan uppgift. För förenkling i komplexa appar grupperas identiska asynkrona anropsstackar i en enda visuell representation. Detta ger mer fullständig information, särskilt i scenarier med många uppgifter.

    Till skillnad från uppgiftsvyn visar fönstret Anropsstack endast anropsstacken för den aktuella tråden, inte för flera uppgifter. Det är ofta bra att visa dem båda tillsammans för en mer fullständig bild av apptillståndet.

    Skärmbild av Anropsstack.

    Tips/Råd

    Fönstret Samtalsstack kan visa information, till exempel ett dödläge, med hjälp av beskrivningen Async cycle.

    Under felsökningen kan du ändra om extern kod ska visas. Om du vill växla funktionen högerklickar du på tabellrubriken Namn i fönstret Anropa stack och väljer eller avmarkerar sedan Visa extern kod. Om du visar extern kod kan du fortfarande använda den här genomgången, men dina resultat kan skilja sig från illustrationerna.

  4. Tryck på F5 igen och felsökningsprogrammet pausar i DoSomeMonkeyBusiness -metoden.

    Skärmbild av Uppgiftsvyn efter F5.

    Den här vyn visar en mer komplett asynkron anropsstack efter att fler asynkrona metoder har lagts till i den interna fortsättningskedjan, vilket inträffar när du använder await och liknande metoder. DoSomeMonkeyBusiness kan eller kanske inte finnas överst i den asynkrona anropsstacken eftersom det är en asynkron metod, men ännu inte har lagts till i fortsättningskedjan. Vi kommer att undersöka varför så är fallet i de steg som följer.

    Den här vyn visar också ikonen för Jungle.MainStatus Blockerad som är blockerad. Detta är informativt, men tyder vanligtvis inte på något problem. En blockerad uppgift är en uppgift som blockeras eftersom den väntar på att en annan uppgift ska slutföras, en händelse som ska signaleras eller ett lås som ska släppas.

  5. Hovra över GobbleUpBananas metoden för att få information om de två trådar som kör aktiviteterna.

    Skärmbild av trådarna som är associerade med anropsstacken.

    Den aktuella tråden visas också i listan Tråd i verktygsfältet Felsökning.

    Skärmbild av den aktuella tråden i verktygsfältet Felsökning.

    Du kan använda trådlistan för att växla felsökningskontexten till en annan tråd.

  6. Tryck på F5 igen så pausar DoSomeMonkeyBusiness felsökningsprogrammet metoden för den andra aktiviteten.

    Skärmbild av Uppgifter-vyn efter andra tryckningen av F5.

    Beroende på tidpunkten för aktivitetskörningen ser du i det här läget antingen separata eller grupperade asynkrona anropsstackar.

    I föregående bild är asynkrona anropsstackar för de två uppgifterna separata eftersom de inte är identiska.

  7. Tryck på F5 igen så visas en lång fördröjning och vyn Uppgifter visar ingen asynkron anropsstackinformation.

    Fördröjningen orsakas av en långvarig aktivitet. I det här exemplet simuleras en tidskrävande uppgift, till exempel en webbbegäran, vilket kan leda till att trådpoolen svälter. Ingenting visas i vyn Uppgifter eftersom du för närvarande inte har pausat felsökningsprogrammet, trots att uppgifter kan blockeras.

    Tips/Råd

    Knappen Bryt alla är ett bra sätt att få information om anropsstacken om ett dödläge inträffar eller om alla tasker och trådar för närvarande blockeras.

  8. Längst upp i IDE:t i verktygsfältet Felsökning väljer du knappen Bryt alla (pausikon), Ctrl + Alt + Bryt.

    Skärmbild av vyn Arbetsuppgifter när du har valt Bryt alla.

    Nära toppen av asynkron anropsstacken i Uppgifter-vyn ser du att GobbleUpBananas är blockerad. I själva verket blockeras två uppgifter vid samma tidpunkt. En blockerad uppgift är inte nödvändigtvis oväntad och innebär inte nödvändigtvis att det finns ett problem. Den observerade fördröjningen i körningen indikerar dock ett problem, och informationen om anropsstacken här visar platsen för problemet.

    På vänster sida av föregående skärmbild anger den curlade gröna pilen den aktuella felsökningskontexten. De två uppgifterna blockeras mb.Wait() i GobbleUpBananas-metoden.

    Fönstret Samtalsstack visar också att den aktuella tråden är blockerad.

    Skärmbild av Samtalsstack när du har valt Bryt alla.

    Anropet till Wait() blockerar trådarna i det synkrona anropet till GobbleUpBananas. Det här är ett exempel på antimönstret sync-over-async, och om detta inträffade i en UI-tråd eller under stora bearbetningsarbetsbelastningar skulle det vanligtvis åtgärdas med en kodkorrigering med hjälp av await. Mer information finns i Felsöka utsvulten trådpool. Information om hur du använder profileringsverktyg för att felsöka utsvulten trådpool finns i Fallstudie: Isolera ett prestandaproblem.

    Det är också av intresse att DoSomeMonkeyBusiness inte visas på anropsstacken. Det är för närvarande schemalagt, körs inte, så det visas bara i asynkron anropsstacken i vyn Uppgifter.

    Tips/Råd

    Felsökningsprogrammet bryter sig in i kod per tråd. Det innebär till exempel att om du trycker på F5 för att fortsätta körningen och appen når nästa brytpunkt kan den brytas in i kod i en annan tråd. Om du behöver hantera detta i felsökningssyfte kan du lägga till ytterligare brytpunkter, lägga till villkorsstyrda brytpunkter eller använda Bryt alla. Mer information om det här beteendet finns i Följ en enda tråd med villkorsstyrda brytpunkter.

Åtgärda exempelkoden

  1. GobbleUpBananas Ersätt metoden med följande kod.

     public async Task GobbleUpBananas(int food) // Previously returned void.
     {
         Console.WriteLine("Trying to gobble up food...");
    
         //Task mb = DoSomeMonkeyBusiness();
         //mb.Wait();
         await DoSomeMonkeyBusiness();
     }
    
  2. I MorningWalk-metoden anropar du GobbleUpBananas med await.

    await GobbleUpBananas(myResult);
    
  3. Välj knappen Starta om (Ctrl + Skift + F5) och tryck sedan på F5 flera gånger tills appen visas som "låser sig".

  4. Tryck på Bryt alla.

    Den här gången GobbleUpBananas körs asynkront. När du bryter visas asynkron anropsstacken.

    Skärmbild av felsökningskontexten efter kodkorrigering.

    Fönstret Samtalsstack är tomt förutom posten ExternalCode .

    Kodredigeraren visar ingenting, förutom att det innehåller ett meddelande som anger att alla trådar kör extern kod.

    Uppgiftvyn ger dock användbar information. DoSomeMonkeyBusiness är överst i asynkron anropsstacken, som förväntat. Detta anger korrekt var den långvariga metoden finns. Det här är användbart för att isolera async/await-problem när den fysiska anropsstacken i fönstret Samtalsstack inte ger tillräcklig information.

Sammanfattning

Den här genomgången visade felsökningsfönstret Parallel Stacks . Använd det här fönstret på appar som använder mönstret async/await.