Dela via


Asynkron programmering med asynkron inväntning

Modellen Task asynkron programmering (TAP) ger ett abstraktionslager över typisk asynkron kodning. I den här modellen skriver du kod som en sekvens med instruktioner, samma som vanligt. Skillnaden är att du kan läsa din uppgiftsbaserade kod när kompilatorn bearbetar varje -instruktion och innan den börjar bearbeta nästa instruktion. För att åstadkomma den här modellen utför kompilatorn många transformeringar för att slutföra varje uppgift. Vissa instruktioner kan initiera arbete och returnera ett Task objekt som representerar det pågående arbetet och kompilatorn måste lösa dessa transformeringar. Målet med asynkron aktivitetsprogrammering är att aktivera kod som läser som en sekvens med instruktioner, men körs i en mer komplicerad ordning. Genomförandet baseras på extern resursallokering och när uppgifterna är slutförda.

Uppgiftens asynkrona programmeringsmodell motsvarar hur personer ger instruktioner för processer som innehåller asynkrona uppgifter. Den här artikeln använder ett exempel med instruktioner för att göra frukost för att visa hur nyckelorden async och await gör det lättare att resonera om kod som innehåller en serie asynkrona instruktioner. Anvisningarna för att göra en frukost kan anges som en lista:

  1. Häll en kopp kaffe.
  2. Värm en panna och stek sedan två ägg.
  3. Stek tre skivor bacon.
  4. Rosta två brödbitar.
  5. Sprid smör och sylt på rostat bröd.
  6. Häll ett glas apelsinjuice.

Om du har erfarenhet av matlagning kan du slutföra dessa instruktioner asynkront. Du börjar värma pannan för ägg och börja sedan steka baconet. Du lägger brödet i brödrosten och börjar sedan laga äggen. I varje steg i processen startar du en uppgift och övergår sedan till andra uppgifter som är redo för din uppmärksamhet.

Matlagningsfrukost är ett bra exempel på asynkront arbete som inte är parallellt. En person (eller tråd) kan hantera alla uppgifter. En person kan göra frukosten asynkront genom att starta nästa uppgift innan föregående uppgift slutförs. Varje matlagningsuppgift fortsätter oavsett om någon aktivt tittar på processen. Så snart du börjar värma pannan för äggen kan du börja steka baconet. När baconet börjar stekas kan du lägga i brödet i brödrosten.

För en parallell algoritm behöver du flera personer som lagar mat (eller flera trådar). En person lagar äggen, en annan steker bacon och så vidare. Varje person fokuserar på en specifik uppgift. Varje person som lagar mat (eller varje tråd) blockeras synkront i väntan på att den aktuella uppgiften ska slutföras: Bacon redo att vända, bröd redo att dyka upp i brödrost och så vidare.

diagram som visar instruktioner för att förbereda frukost som en lista över sju sekventiella uppgifter som slutförts på 30 minuter.

Överväg samma lista med synkrona instruktioner skrivna som C#-kodinstruktioner:

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            Egg eggs = FryEggs(2);
            Console.WriteLine("eggs are ready");

            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");

            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

Om du tolkar dessa instruktioner som en dator skulle göra tar det cirka 30 minuter att förbereda frukosten. Varaktigheten är summan av de enskilda aktivitetstiderna. Datorn blockerar vid varje instruktion tills allt arbete har utförts och fortsätter sedan till nästa uppgiftsuttryck. Den här metoden kan ta mycket tid. I frukostexemplet skapar datormetoden en otillfredsställande frukost. Senare uppgifter i den synkrona listan, som att rosta brödet, startar inte förrän tidigare uppgifter har slutförts. Lite mat blir kall innan frukosten är redo att serveras.

Om du vill att datorn ska köra instruktioner asynkront måste du skriva asynkron kod. När du skriver klientprogram vill du att användargränssnittet ska vara responsivt för användarinmatning. Ditt program bör inte frysa all interaktion när du laddar ned data från webben. När du skriver serverprogram vill du inte blockera trådar som kan hantera andra begäranden. Att använda synkron kod när det finns asynkrona alternativ försvårar möjligheten att skala ut mindre kostsamt. Du betalar för blockerade trådar.

Lyckade moderna appar kräver asynkron kod. Utan språkstöd kräver skrivning av asynkron kod återanrop, slutförandehändelser eller annat sätt att dölja kodens ursprungliga avsikt. Fördelen med synkron kod är den stegvisa åtgärden som gör det enkelt att skanna och förstå. Traditionella asynkrona modeller tvingar dig att fokusera på kodens asynkrona karaktär, inte på de grundläggande åtgärderna i koden.

Blockera inte, vänta i stället

Föregående kod visar en olycklig programmeringspraxis: Skriva synkron kod för att utföra asynkrona åtgärder. Koden blockerar den aktuella tråden från att utföra något annat arbete. Koden avbryter inte tråden när aktiviteter körs. Resultatet av den här modellen liknar att stirra på brödrosten när du har lagt i brödet. Du ignorerar eventuella avbrott och startar inte andra uppgifter förrän brödet dyker upp. Du tar inte smöret och sylten ur kylskåpet. Du kanske missar att upptäcka en brand som börjar på spisen. Du vill både rosta brödet och hantera andra problem på samma gång. Samma sak gäller för din kod.

Du kan börja med att uppdatera koden så att tråden inte blockeras när aktiviteter körs. Nyckelordet await ger ett icke-blockerande sätt att starta en uppgift och sedan fortsätta körningen när aktiviteten är klar. En enkel asynkron version av frukostkoden ser ut som följande kodfragment:

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");

    Egg eggs = await FryEggsAsync(2);
    Console.WriteLine("eggs are ready");

    Bacon bacon = await FryBaconAsync(3);
    Console.WriteLine("bacon is ready");

    Toast toast = await ToastBreadAsync(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}

Koden uppdaterar metodkropparna för FryEggsAsync, FryBaconAsyncoch ToastBreadAsync för att returnera Task<Egg>, Task<Bacon>och Task<Toast> objekt. Metodnamnen innehåller suffixet "Async". Metoden Main returnerar det Task objektet, även om det inte har ett return uttryck, vilket är avsiktligt. För mer information, se Utvärdering av en asynkron funktion som returnerar void.

Not

Den uppdaterade koden drar ännu inte nytta av viktiga funktioner i asynkron programmering, vilket kan leda till kortare slutförandetider. Koden bearbetar aktiviteterna på ungefär samma tid som den ursprungliga synkrona versionen. Fullständiga metodimplementeringar finns i den slutliga versionen av koden senare i den här artikeln.

Nu ska vi använda frukostexemplet på den uppdaterade koden. Tråden blockerar inte medan äggen eller baconet lagar mat, men koden startar inte heller andra uppgifter förrän det nuvarande arbetet är klart. Du lägger fortfarande brödet i brödrosten och stirrar på brödrosten tills brödet dyker upp, men du kan nu svara på avbrott. På en restaurang där flera beställningar görs kan kocken starta en ny beställning medan en annan redan lagar mat.

I den uppdaterade koden blockeras inte den tråd som hanterar frukosten medan den väntar på en annan uppgift som inte är avslutad. För vissa program är den här ändringen allt du behöver. Du kan aktivera din app för att stödja användarinteraktion medan data laddas ned från webben. I andra scenarier kanske du vill starta andra aktiviteter medan du väntar på att den föregående aktiviteten ska slutföras.

Starta aktiviteter samtidigt

För de flesta åtgärder vill du starta flera oberoende uppgifter omedelbart. När varje uppgift slutförs initierar du annat arbete som är redo att startas. När du tillämpar den här metoden på frukostexemplet kan du förbereda frukosten snabbare. Du får också allt klart vid ungefär samma tidpunkt, så att du kan njuta av en varm frukost.

Klassen System.Threading.Tasks.Task och relaterade typer är klasser som du kan använda för att tillämpa den här typen av resonemang på aktiviteter som pågår. Med den här metoden kan du skriva kod som liknar hur du skapar frukost i verkligheten. Du börjar laga ägg, bacon och rostat bröd samtidigt. Eftersom varje matobjekt kräver åtgärder, vänder du din uppmärksamhet mot den uppgiften, tar hand om åtgärden och väntar sedan på något annat som kräver din uppmärksamhet.

I koden startar du en uppgift och håller fast vid det Task objekt som representerar arbetet. Du använder metoden await för aktiviteten för att fördröja arbetet tills resultatet är klart.

Tillämpa dessa ändringar på frukostkoden. Det första steget är att lagra uppgifter för åtgärder när de startas, i stället för att använda uttrycket await:

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");

Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Breakfast is ready!");

Dessa revisioner hjälper inte till att få din frukost redo snabbare. Uttrycket await tillämpas på alla aktiviteter så snart de startar. Nästa steg är att flytta await uttryck för bacon och ägg till slutet av metoden, innan du serverar frukosten:

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

Nu har du en asynkront förberedd frukost som tar cirka 20 minuter att förbereda. Den totala tillagningstiden minskas eftersom vissa aktiviteter körs samtidigt.

diagram som visar instruktioner för att förbereda frukost som åtta asynkrona uppgifter som slutförs på cirka 20 minuter, där tyvärr äggen och bacon brinner.

Koduppdateringarna förbättrar förberedelseprocessen genom att minska koktiden, men de introducerar en regression genom att bränna äggen och baconet. Du startar alla asynkrona uppgifter samtidigt. Du väntar bara på varje aktivitet när du behöver resultatet. Koden kan likna programmet i ett webbprogram som skickar begäranden till olika mikrotjänster och sedan kombinerar resultatet till en enda sida. Du gör alla begäranden omedelbart och tillämpar sedan uttrycket await på alla dessa uppgifter och skriver webbsidan.

Stöd komposition med uppgifter

De tidigare kodrevisionerna hjälper till att göra allt klart för frukost samtidigt, förutom rostbrödet. Processen att göra rostat bröd är en sammansättning av en asynkron åtgärd (rosta brödet) med synkrona åtgärder (sprid smör och sylt på rostat bröd). Det här exemplet illustrerar ett viktigt begrepp om asynkron programmering:

Viktig

Sammansättningen av en asynkron åtgärd följt av synkront arbete är en asynkron åtgärd. Ett annat sätt att säga det, om någon del av en operation är asynkron, är hela operationen asynkron.

I de tidigare uppdateringarna har du lärt dig hur du använder Task eller Task<TResult> objekt för att lagra aktiviteter som körs. Du väntar på varje aktivitet innan du använder resultatet. Nästa steg är att skapa metoder som representerar kombinationen av annat arbete. Innan du serverar frukost vill du vänta på uppgiften som representerar rostning av brödet innan du sprider smöret och sylten.

Du kan representera det här arbetet med följande kod:

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);

    return toast;
}

Metoden MakeToastWithButterAndJamAsync har async-modifieraren i sin signatur som signalerar till kompilatorn att metoden innehåller ett await uttryck och innehåller asynkrona åtgärder. Metoden representerar den uppgift som rostar brödet och sprider sedan smöret och sylten. Metoden returnerar ett Task<TResult> objekt som representerar sammansättningen av de tre åtgärderna.

Det reviderade huvudkodblocket ser nu ut så här:

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");

    var eggsTask = FryEggsAsync(2);
    var baconTask = FryBaconAsync(3);
    var toastTask = MakeToastWithButterAndJamAsync(2);

    var eggs = await eggsTask;
    Console.WriteLine("eggs are ready");

    var bacon = await baconTask;
    Console.WriteLine("bacon is ready");

    var toast = await toastTask;
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}

Den här kodändringen illustrerar en viktig teknik för att arbeta med asynkron kod. Du skapar aktiviteter genom att separera åtgärderna till en ny metod som returnerar en aktivitet. Du kan välja när du ska vänta på den uppgiften. Du kan starta andra aktiviteter samtidigt.

Hantera asynkrona undantag

Fram tills nu förutsätter koden implicit att alla uppgifter har slutförts. Asynkrona metoder utlöser undantag, precis som deras synkrona motsvarigheter. Målen för asynkront stöd för undantag och felhantering är desamma som för asynkront stöd i allmänhet. Det bästa sättet är att skriva kod som läser som en serie synkrona instruktioner. Aktiviteter utlöser undantag när de inte kan slutföras. Klientkoden kan fånga dessa undantag när await-uttrycket tillämpas på en startad uppgift.

I frukostexemplet antar du att brödrosten fattar eld medan brödet rostas. Du kan simulera det problemet genom att ändra metoden ToastBreadAsync så att den matchar följande kod:

private static async Task<Toast> ToastBreadAsync(int slices)
{
    for (int slice = 0; slice < slices; slice++)
    {
        Console.WriteLine("Putting a slice of bread in the toaster");
    }
    Console.WriteLine("Start toasting...");
    await Task.Delay(2000);
    Console.WriteLine("Fire! Toast is ruined!");
    throw new InvalidOperationException("The toaster is on fire");
    await Task.Delay(1000);
    Console.WriteLine("Remove toast from toaster");

    return new Toast();
}

Not

När du kompilerar den här koden visas en varning om kod som inte kan nås. Det här felet är avsiktligt. När brödrosten har fattat eld fortsätter inte åtgärderna normalt och koden returnerar ett fel.

När du har ändrat koden kör du programmet och kontrollerar utdata:

Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 slices of bacon in the pan
Cooking first side of bacon...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a slice of bacon
Flipping a slice of bacon
Flipping a slice of bacon
Cooking the second side of bacon...
Cracking 2 eggs
Cooking the eggs ...
Put bacon on plate
Put eggs on plate
Eggs are ready
Bacon is ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
   at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.cs:line 65
   at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.cs:line 36
   at AsyncBreakfast.Program.Main(String[] args) in Program.cs:line 24
   at AsyncBreakfast.Program.<Main>(String[] args)

Observera att en hel del uppgifter slutförs mellan den tidpunkt då brödrosten fattar eld och systemet observerar undantaget. När en aktivitet som körs asynkront utlöser ett undantag den aktiviteten. Objektet Task innehåller undantaget som genereras i egenskapen Task.Exception. Uppgifter med fel utlöser ett undantag när await-uttrycket tillämpas på uppgiften.

Det finns två viktiga mekanismer för att förstå den här processen:

  • Hur ett undantag lagras i en felaktig uppgift
  • Hur ett undantag packas upp och återkastas när koden väntar (await) på en felande uppgift

När kod som körs asynkront genererar ett undantag lagras undantaget i Task-objektet. Egenskapen Task.Exception är ett System.AggregateException objekt eftersom fler än ett undantag kan genereras under asynkront arbete. Undantag som utlöses läggs till i samlingen AggregateException.InnerExceptions. Om egenskapen Exception är null skapas ett nytt AggregateException objekt och undantaget som genereras är det första objektet i samlingen.

Det vanligaste scenariot för en felaktig uppgift är att egenskapen Exception innehåller exakt ett undantag. När koden väntar på en felaktig uppgift, så överväxlar den det första AggregateException.InnerExceptions undantaget i samlingen. Det här resultatet är orsaken till att utdata från exemplet visar ett System.InvalidOperationException objekt i stället för ett AggregateException objekt. Om du extraherar det första inre undantaget kan du arbeta med asynkrona metoder på ett så liknande sätt som möjligt för att arbeta med deras synkrona motsvarigheter. Du kan undersöka egenskapen Exception i koden när ditt scenario kan generera flera undantag.

Tips

Den rekommenderade metoden är att alla argumentverifieringsfel visas synkront från metoder som returnerar uppgifter. Mer information och exempel finns i Undantag i uppgiftsreturmetoder.

Innan du fortsätter till nästa avsnitt kommenterar du ut följande två satser i din ToastBreadAsync-metod. Du vill inte starta en ny brand:

Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");

Tillämpa await-uttryck på uppgifter effektivt

Du kan förbättra serien med await uttryck i slutet av föregående kod med hjälp av metoder för klassen Task. Ett API är metoden WhenAll, som returnerar ett Task objekt som slutförs när alla uppgifter i argumentlistan är slutförda. Följande kod visar den här metoden:

await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");

Ett annat alternativ är att använda metoden WhenAny, som returnerar ett Task<Task> objekt som slutförs när något av argumenten har slutförts. Du kan vänta på den returnerade uppgiften eftersom du vet att aktiviteten är klar. Följande kod visar hur du kan använda metoden WhenAny för att vänta på att den första aktiviteten ska slutföras och sedan bearbeta resultatet. När du har bearbetat resultatet från den slutförda aktiviteten tar du bort den slutförda aktiviteten från listan över aktiviteter som skickas till metoden WhenAny.

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);
    if (finishedTask == eggsTask)
    {
        Console.WriteLine("Eggs are ready");
    }
    else if (finishedTask == baconTask)
    {
        Console.WriteLine("Bacon is ready");
    }
    else if (finishedTask == toastTask)
    {
        Console.WriteLine("Toast is ready");
    }
    await finishedTask;
    breakfastTasks.Remove(finishedTask);
}

Lägg märke till await finishedTask; uttryck i slutet av kodfragmentet. Uttrycket await Task.WhenAny väntar inte på den färdiga aktiviteten, utan väntar i stället på det Task objekt som returneras av metoden Task.WhenAny. Resultatet av Task.WhenAny-metoden är den slutförda (eller felaktiga) uppgiften. Det bästa sättet är att vänta på uppgiften igen, även när du vet att aktiviteten är klar. På så sätt kan du hämta uppgiftsresultatet, eller se till att eventuella undantag som gör att uppgiften misslyckas utlöses.

Granska den slutliga koden

Så här ser den slutliga versionen av koden ut:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static async Task Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            var eggsTask = FryEggsAsync(2);
            var baconTask = FryBaconAsync(3);
            var toastTask = MakeToastWithButterAndJamAsync(2);

            var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                await finishedTask;
                breakfastTasks.Remove(finishedTask);
            }

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

Koden slutför de asynkrona frukostuppgifterna på cirka 15 minuter. Den totala tiden minskas eftersom vissa aktiviteter körs samtidigt. Koden övervakar flera uppgifter samtidigt och vidtar endast åtgärder efter behov.

diagram som visar instruktioner för att förbereda frukost som sex asynkrona uppgifter som slutförs på cirka 15 minuter och koden övervakar eventuella avbrott.

Den slutliga koden är asynkron. Det återspeglar mer exakt hur en person kan laga frukost. Jämför den slutliga koden med det första kodexemplet i artikeln. Kärnåtgärderna är fortfarande tydliga genom att läsa koden. Du kan läsa den slutliga koden på samma sätt som du läser listan med instruktioner för att göra en frukost, som visas i början av artikeln. Språkfunktionerna för nyckelorden async och await ger den översättning som varje person gör för att följa de skriftliga instruktionerna: Starta uppgifter som du kan och blockera inte medan du väntar på att aktiviteter ska slutföras.

Nästa steg