Delen via


Fouten opsporen in een asynchrone toepassing

Deze zelfstudie laat zien hoe u de takenweergave van het venster Parallelle stacks gebruikt om fouten op te sporen in een C#-asynchrone toepassing. Dit venster helpt u inzicht te hebben in het runtimegedrag van code dat gebruikmaakt van het async/await-patroon, ook wel het asynchrone patroon (TAP) op basis van taken genoemd.

Voor apps die gebruikmaken van de Task Parallel Library (TPL), maar niet het async/await-patroon, of voor C++-apps met behulp van de Gelijktijdigheidsruntime, gebruikt u de threads-weergave in het venster Parallelle stacks voor foutopsporing. Zie Debug a deadlock and View threads and tasks in the Parallel Stacks window (Threads en taken weergeven) voor meer informatie.

De Takenweergave helpt u bij het volgende:

  • Bekijk aanroepstackvisualisaties voor apps die gebruikmaken van het asynchrone/wachtpatroon. In deze scenario's biedt de weergave Taken een vollediger beeld van de status van uw app.

  • Identificeer asynchrone code die is gepland om te worden uitgevoerd, maar nog niet wordt uitgevoerd. Een HTTP-aanvraag die geen gegevens heeft geretourneerd, wordt bijvoorbeeld waarschijnlijk weergegeven in de weergave Taken in plaats van in de weergave Threads, waardoor u het probleem kunt isoleren.

  • Hulp bij het identificeren van problemen zoals het synchronisatie-over-asynchroon patroon, samen met aanwijzingen voor potentiële problemen zoals geblokkeerde of wachtende taken. Het sync-over-asynchrone codepatroon verwijst naar code die asynchrone methoden op een synchrone manier aanroept. Dit is bekend om threads te blokkeren en is de meest voorkomende oorzaak van starvatie van threadpools.

Asynchrone aanroepstacks

De weergave Taken in Parallelle Stacks biedt een visualisatie voor asynchrone oproepstapels, zodat u kunt zien wat er gebeurt (of moet gebeuren) in uw toepassing.

Hier volgen enkele belangrijke punten die u moet onthouden bij het interpreteren van gegevens in de weergave Taken.

  • Asynchrone aanroepstacks zijn logische of virtuele aanroepstacks, niet fysieke aanroepstacks die de stack vertegenwoordigen. Wanneer u met asynchrone code werkt (bijvoorbeeld met behulp van het await trefwoord), biedt het foutopsporingsprogramma een weergave van de 'asynchrone aanroepstacks' of 'virtuele aanroepstacks'. Asynchrone aanroepstacks verschillen van thread-gebaseerde aanroepstacks, of 'fysieke stacks', omdat asynchrone aanroepstacks niet per se op een fysieke thread worden uitgevoerd. In plaats daarvan zijn de asynchrone aanroepstacks vervolgen of 'beloften' van code die in de toekomst asynchroon worden uitgevoerd. De aanroepstacks worden gemaakt met continuaties.

  • Asynchrone code die is gepland maar momenteel niet wordt uitgevoerd, verschijnt niet op de fysieke aanroepstack, maar zou wel moeten verschijnen op de asynchrone aanroepstack in de Tasks view. Als u threads blokkeert met behulp van methoden zoals .Wait of .Result, ziet u mogelijk de code in de fysieke call stack.

  • Asynchrone virtuele aanroepstacks zijn niet altijd intuïtief, vanwege vertakkingen die het gevolg zijn van het gebruik van methode-aanroepen zoals .WaitAny of .WaitAll.

  • Het venster Oproepstack kan handig zijn in combinatie met de weergave Taken, omdat de fysieke aanroepstack voor de huidige uitvoerthread wordt weergegeven.

  • Identieke secties van de virtuele aanroepstack worden gegroepeerd om de visualisatie voor complexe apps te vereenvoudigen.

    In de volgende conceptuele animatie ziet u hoe groepering wordt toegepast op virtuele aanroepstacks. Alleen identieke segmenten van een virtuele aanroepstack worden gegroepeerd. Beweeg de muisaanwijzer over een gegroepeerde aanroepstack om de threads uit te voeren die de taken uitvoeren.

    Afbeelding van de groepering van virtuele aanroepstacks.

C#-voorbeeld

De voorbeeldcode in dit scenario is bedoeld voor een toepassing die een dag in het leven van een gorilla simuleert. Het doel van de oefening is om te begrijpen hoe u de weergave Taken van het venster Parallelle stacks gebruikt om fouten in een asynchrone toepassing op te sporen.

Dit voorbeeld omvat het gebruik van het sync-over-async antipatroon, wat kan leiden tot uitputting van threadpools.

Om de aanroepstack intuïtief te maken, voert de voorbeeld-app de volgende opeenvolgende stappen uit:

  1. Hiermee maakt u een object dat een gorilla vertegenwoordigt.
  2. Gorilla wordt wakker.
  3. Gorilla gaat op een ochtendwandeling.
  4. Gorilla vindt bananen in de jungle.
  5. Gorilla eet.
  6. Gorilla neemt deel aan apenhandel.

Het voorbeeldproject maken

  1. Open Visual Studio en maak een nieuw project.

    Als het startvenster niet is geopend, kiest u Bestand>Startvenster.

    Kies Nieuw project in het startvenster.

    In het venster Een nieuw project maken voer console in het zoekvak in. Kies vervolgens C# in de lijst Taal en kies vervolgens Windows in de lijst Platform.

    Nadat u de taal- en platformfilters hebt toegepast, kiest u de console-app voor .NET en kiest u vervolgens Volgende.

    Opmerking

    Als u de juiste sjabloon niet ziet, gaat u naar Tools>Get Tools and Features..., waarmee het installatieprogramma van Visual Studio wordt geopend. Kies de .NET-desktopontwikkelingswerklast en selecteer vervolgens Wijzigen.

    Typ in het venster Uw nieuwe project configureren een naam of gebruik de standaardnaam in het vak Projectnaam . Kies vervolgens Volgende.

    Kies voor .NET het aanbevolen doelframework of .NET 8 en kies vervolgens Maken.

    Er verschijnt een nieuw consoleproject. Nadat het project is gemaakt, wordt er een bronbestand weergegeven.

  2. Open het codebestand .cs in het project. Verwijder de inhoud om een leeg codebestand te maken.

  3. Plak de volgende code voor de gekozen taal in het lege codebestand.

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

    Nadat u het codebestand hebt bijgewerkt, slaat u de wijzigingen op en bouwt u de oplossing.

  4. Selecteer in het menu Bestand de optie Alles opslaan.

  5. Selecteer in het menu BuildBuild Solution.

Gebruik de takenweergave van het venster Parallelle stacks

  1. Selecteer in het menu Foutopsporingstarten de foutopsporing (of F5) en wacht tot de eerste Debugger.Break() is bereikt.

  2. Druk eenmaal op F5 en het foutopsporingsprogramma wordt opnieuw onderbroken op dezelfde Debugger.Break() regel.

    Dit pauzeert bij de tweede aanroep naar Gorilla_Start, die plaatsvindt binnen de tweede asynchrone taak.

  3. Selecteer Debuggen > Windows > Parallelle Stacks om het venster Parallelle Stacks te openen en selecteer vervolgens Taken in de vervolgkeuzelijst Weergave in het venster.

    Schermafbeelding van de weergave Taken in het venster Parallelle Stacks.

    Let op dat de labels van de asynchrone aanroepstapels 2 Asynchrone Logische Stapels beschrijven. Toen u voor het laatst op F5 drukt, hebt u een andere taak gestart. Voor vereenvoudiging in complexe apps worden identieke asynchrone aanroepstacks gegroepeerd in één visuele weergave. Dit biedt meer volledige informatie, met name in scenario's met veel taken.

    In tegenstelling tot de weergave Taken toont het venster Aanroepstack alleen de aanroepstack voor de huidige thread, niet voor meerdere taken. Het is vaak handig om beide samen te bekijken voor een vollediger beeld van de status van de app.

    Schermopname van Oproepstack.

    Aanbeveling

    In het venster Gespreksstack kunt u informatie weergeven, zoals een impasse, met behulp van de beschrijving Async cycle.

    Tijdens foutopsporing kunt u in- of uitschakelen of externe code wordt weergegeven. Als u de functie wilt in- of uitschakelen klikt u met de rechtermuisknop op de kolomkop 'Name' van het venster 'Call Stack' en schakelt u 'Externe code weergeven' in of uit. Als u externe code weergeeft, kunt u deze procedure nog steeds gebruiken, maar de resultaten kunnen afwijken van de illustraties.

  4. Druk nogmaals op F5 en het foutopsporingsprogramma wordt onderbroken in de DoSomeMonkeyBusiness methode.

    Schermopname van de weergave Taken na F5.

    In deze weergave ziet u een volledigere asynchrone aanroepstack nadat er meer asynchrone methoden zijn toegevoegd aan de interne vervolgketen, die optreedt bij het gebruik van await en vergelijkbare methoden. DoSomeMonkeyBusiness kan wel of niet boven aan de asynchrone aanroepstack aanwezig zijn omdat het een asynchrone methode is, maar nog niet is toegevoegd aan de vervolgketen. We verkennen waarom dit het geval is in de volgende stappen.

    In deze weergave ziet u ook het geblokkeerde pictogram voor Jungle.MainStatus Geblokkeerd. Dit is informatief, maar duidt meestal niet op een probleem. Een geblokkeerde taak is een taak die wordt geblokkeerd omdat deze wacht op een andere taak om te voltooien, een gebeurtenis die moet worden gesignaleerd of een vergrendeling die moet worden vrijgegeven.

  5. Beweeg de muisaanwijzer over de GobbleUpBananas methode om informatie op te halen over de twee threads die de taken uitvoeren.

    Schermopname van de threads die zijn gekoppeld aan de aanroepstack.

    De huidige thread wordt ook weergegeven in de lijst Thread op de werkbalk Foutopsporing.

    Schermopname van de huidige thread op de werkbalk Foutopsporing.

    U kunt de threadlijst gebruiken om de context van het foutopsporingsprogramma over te schakelen naar een andere thread.

  6. Druk nogmaals op F5 en het foutopsporingsprogramma wordt onderbroken in de DoSomeMonkeyBusiness methode voor de tweede taak.

    Schermopname van de weergave Taken na tweede F5.

    Afhankelijk van de timing van de taakuitvoering ziet u op dit moment afzonderlijke of gegroepeerde asynchrone aanroepstacks.

    In de vorige afbeelding zijn de asynchrone aanroepstacks voor de twee taken gescheiden omdat ze niet identiek zijn.

  7. Druk nogmaals op F5 en u ziet een lange vertraging en in de weergave Taken worden geen asynchrone aanroepstackgegevens weergegeven.

    De vertraging wordt veroorzaakt door een langlopende taak. In dit voorbeeld wordt een langlopende taak, zoals bijvoorbeeld een webaanvraag, gesimuleerd, wat kan resulteren in uitputting van threadpools. Er wordt niets weergegeven in de weergave Taken, omdat, hoewel taken mogelijk worden geblokkeerd, u momenteel niet gepauzeerd bent in de debugger.

    Aanbeveling

    De knop Alles onderbreken is een goede manier om callstackinformatie op te halen als er een deadlock optreedt of als alle taken en threads op dit moment geblokkeerd zijn.

  8. Selecteer boven aan de IDE in de werkbalk Foutopsporing de knop Alle onderbreken (pauzepictogram), Ctrl + Alt + Break.

    Schermopname van de weergave Taken na het selecteren van Alles verbreken.

    Dichtbij de bovenkant van de asynchrone aanroepstack in de weergave Taken ziet u dat GobbleUpBananas is geblokkeerd. In feite worden twee taken op hetzelfde punt geblokkeerd. Een geblokkeerde taak is niet noodzakelijkerwijs onverwacht en betekent niet noodzakelijkerwijs dat er een probleem is. De waargenomen vertraging in de uitvoering duidt echter op een probleem en de informatie over de aanroepstack hier toont de locatie van het probleem.

    Aan de linkerkant van de vorige schermopname geeft de gekrulde groene pijl de huidige context van het foutopsporingsprogramma aan. De twee taken worden geblokkeerd mb.Wait() in de GobbleUpBananas methode.

    In het venster Oproepstack ziet u ook dat de huidige thread is geblokkeerd.

    Schermopname van Call Stack nadat u Onderbreken Alles hebt geselecteerd.

    De aanroep naar Wait() blokkeert de threads binnen de synchrone aanroep naar GobbleUpBananas. Dit is een voorbeeld van het "sync-over-async"-antipatroon, en als dit zich voordoet op een UI-thread of onder grote verwerkingsbelasting, wordt het doorgaans opgelost met een code-aanpassing met behulp van await. Zie Debug thread pool starvation voor meer informatie. Zie Case-study: Een prestatieprobleem isoleren voor het opsporen van thread pool uitputting met behulp van profileringsprogramma's.

    Het is ook interessant dat DoSomeMonkeyBusiness niet op de aanroepstack verschijnt. Het is momenteel gepland en wordt niet uitgevoerd, dus het wordt alleen weergegeven in de asynchrone aanroepstack in de Takenweergave.

    Aanbeveling

    Het foutopsporingsprogramma wordt onderverdeeld in code per thread. Dit betekent bijvoorbeeld dat als u op F5 drukt om door te gaan met de uitvoering en de app het volgende onderbrekingspunt bereikt, deze in code op een andere thread kan inbreken. Als u dit wilt beheren voor foutopsporingsdoeleinden, kunt u extra onderbrekingspunten toevoegen, voorwaardelijke onderbrekingspunten toevoegen of Alles onderbreken gebruiken. Zie Een enkele thread volgen met voorwaardelijke onderbrekingspunten voor meer informatie over dit gedrag.

De voorbeeldcode herstellen

  1. Vervang de GobbleUpBananas methode door de volgende code.

     public async Task GobbleUpBananas(int food) // Previously returned void.
     {
         Console.WriteLine("Trying to gobble up food...");
    
         //Task mb = DoSomeMonkeyBusiness();
         //mb.Wait();
         await DoSomeMonkeyBusiness();
     }
    
  2. Roep in de MorningWalk methode GobbleUpBananas aan met behulp van await.

    await GobbleUpBananas(myResult);
    
  3. Selecteer de knop Opnieuw opstarten (Ctrl+ Shift + F5) en druk meerdere keren op F5 totdat de app lijkt te hangen.

  4. Druk op Alles verbreken.

    Deze keer wordt GobbleUpBananas deze keer asynchroon uitgevoerd. Wanneer u pauzeert, ziet u de asynchrone aanroepstack.

    Schermopname van de context van het foutopsporingsprogramma na het herstellen van de code.

    Het venster Oproepstack is leeg, met uitzondering van de ExternalCode vermelding.

    In de code-editor wordt niets weergegeven, behalve dat er een bericht wordt weergegeven dat alle threads externe code uitvoeren.

    De weergave van Taken biedt nuttige informatie, echter. DoSomeMonkeyBusiness staat boven aan de asynchrone aanroepstack, zoals verwacht. Dit geeft ons op de juiste manier aan waar de langlopende methode zich bevindt. Dit is nuttig om async/await-problemen te isoleren wanneer de fysieke Call Stack in het venster Call Stack onvoldoende details biedt.

Samenvatting

In deze walkthrough is het debugger-venster Parallel Stacks gedemonstreerd. Gebruik dit venster voor apps die gebruikmaken van het asynchrone/wachtpatroon.