Sdílet prostřednictvím


Asynchronní programování s async a await

Model asynchronního programování úloh (TAP) poskytuje abstraktní vrstvu nad typickým asynchronním kódováním. V tomto modelu napíšete kód jako posloupnost příkazů, stejně jako obvykle. Rozdíl je, že kód založený na úlohách si můžete přečíst, protože kompilátor zpracovává každý příkaz a před zahájením zpracování dalšího příkazu. Aby bylo možné tento model provést, kompilátor provádí mnoho transformací, aby dokončil každou úlohu. Některé příkazy mohou zahájit práci a vrátit objekt Task, který představuje probíhající práci a kompilátor musí tyto transformace vyřešit. Cílem asynchronního programování úkolů je umožnit kód, který čte jako posloupnost příkazů, ale provádí se v složitějším pořadí. Provádění je založeno na přidělování externích zdrojů a po dokončení úkolů.

Model asynchronního programování úkolů je podobný tomu, jak lidé poskytují pokyny pro procesy, které zahrnují asynchronní úlohy. Tento článek používá příklad s pokyny k přípravě snídaně, aby ukázal, jak klíčová slova async a await usnadňují uvažování o kódu, který obsahuje řadu asynchronních instrukcí. Pokyny k vytvoření snídaně mohou být uvedeny jako seznam:

  1. Nalít šálek kávy.
  2. Zahřejte pánev, pak smažte dvě vejce.
  3. Smažte tři plátky slaniny.
  4. Opečte dva plátky chleba.
  5. Rozložte máslo a džem na toast.
  6. Nalít sklenici pomerančové šťávy.

Pokud máte zkušenosti s vařením, můžete tyto pokyny dokončit asynchronně. Začnete ohřívat pánev pro vejce a pak začnete smažit slaninu. Chleba vložíte do toustovače a pak začnete vařit vejce. V každém kroku procesu spustíte úkol a pak přejdete na jiné úkoly, které jsou připravené pro vaši pozornost.

Vaření snídaně je dobrým příkladem asynchronní práce, která není paralelní. Jedna osoba (nebo vlákno) může zpracovávat všechny úkoly. Jedna osoba může snídani asynchronně provést spuštěním dalšího úkolu před dokončením předchozího úkolu. Každý úkol vaření postupuje bez ohledu na to, jestli někdo aktivně sleduje proces. Jakmile začnete ohřívat pánev pro vejce, můžete začít s smažením slaniny. Poté, co slanina začne vařit, můžete dát chléb do toustovače.

Pro paralelní algoritmus potřebujete více lidí, kteří uvaří (nebo více vláken). Jedna osoba uvaří vejce, další osmaží slaninu a tak dále. Každá osoba se zaměřuje na svůj konkrétní úkol. Každá osoba, která vaří (nebo každý vlákno), je synchronně blokována při čekání na dokončení aktuálního úkolu: slanina je připravena k otočení, chléb se chystá vyskočit z toustovače a tak dále.

diagram, který zobrazuje pokyny pro přípravu snídaně jako seznam sedmi sekvenčních úkolů dokončených během 30 minut.

Představte si stejný seznam synchronních instrukcí, které jsou napsané jako příkazy kódu jazyka C#:

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

Pokud tyto pokyny interpretujete jako počítač, snídaně trvá asi 30 minut, než se připraví. Doba trvání je součet jednotlivých časů úkolů. Počítač blokuje každý příkaz, dokud není veškerá práce dokončena, a poté pokračuje na další úkol. Tento přístup může trvat výrazně dlouho. V příkladu snídaně vytvoří počítačová metoda neuspokojivou snídani. Pozdější úkoly v synchronním seznamu, jako je opékání chleba, se nespustí, dokud nebudou dřívější úkoly dokončeny. Některé jídlo je studené, než je snídaně připravena k obsluhě.

Pokud chcete, aby počítač prováděl instrukce asynchronně, musíte napsat asynchronní kód. Při psaní klientských programů chcete, aby uživatelské rozhraní reagovalo na uživatelský vstup. Při stahování dat z webu by vaše aplikace neměla ukotvit veškerou interakci. Při psaní serverových programů nechcete blokovat vlákna, která by mohla obsluhovat jiné požadavky. Použití synchronního kódu v případech, kdy existují asynchronní alternativy, snižuje vaše schopnost škálovat kapacitu méně nákladně. Platíte za blokovaná vlákna.

Úspěšné moderní aplikace vyžadují asynchronní kód. Bez podpory jazyka vyžaduje psaní asynchronního kódu zpětná volání, události dokončení nebo jiné prostředky, které zakrývají původní záměr kódu. Výhodou synchronního kódu je postupný způsob, který usnadňuje skenování a pochopit. Tradiční asynchronní modely vás přinutí zaměřit se na asynchronní povahu kódu, ne na základní akce kódu.

Neblokujte, místo toho čekejte

Předchozí kód zdůrazňuje nešťastný programovací postup: Psaní synchronního kódu pro provádění asynchronních operací. Kód blokuje aktuální vlákno, aby provádělo jakoukoli jinou práci. Kód nepřeruší vlákno v době, kdy jsou spuštěné úlohy. Výsledek tohoto modelu se podobá zírání na toustovač po vložení chleba. Ignorujete všechna přerušení a nezačínáte další úkoly, dokud chleba nevyskočí. Nevezmeš si máslo a marmeládu z chladničky. Můžete přehlédnout, že na sporáku začíná hořet. Chcete si přichystnout chleba a současně zvládnout další obavy. Totéž platí pro váš kód.

Můžete začít aktualizací kódu, aby vlákno neblokoval, když jsou spuštěné úlohy. Klíčové slovo await poskytuje odblokující způsob spuštění úkolu a po dokončení úkolu pokračujte v provádění. Jednoduchá asynchronní verze kódu snídaně vypadá jako následující fragment kódu:

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

Kód aktualizuje původní těla metod FryEggs, FryBacon a ToastBread tak, aby vracely objekty Task<Egg>, Task<Bacon> a Task<Toast> v uvedeném pořadí. Aktualizované názvy metod zahrnují příponu "Async": FryEggsAsync, FryBaconAsynca ToastBreadAsync. Metoda Main vrátí objekt Task, i když nemá výraz return, což je záměr. Pro více informací si přečtěte Vyhodnocení asynchronní funkce vracející void.

Poznámka

Aktualizovaný kód zatím nevyužívá klíčové funkce asynchronního programování, což může vést k kratší době dokončení. Kód zpracovává úlohy přibližně ve stejné době jako počáteční synchronní verze. Úplné implementace metod najdete v konečné verzi kódu dále v tomto článku.

Pojďme na aktualizovaný kód použít příklad snídaně. Vlákno neblokuje, zatímco se vaří vejce nebo slanina, ale kód také nespustí další úkoly, dokud se aktuální práce nedokončí. Stále dáváte chléb do toustovače a díváte se na toustovač, dokud chleba nevyskočí, ale nyní můžete reagovat na vyrušení. V restauraci, kde se podává více objednávek, může kuchař začít novou objednávku, zatímco jiný už vaření.

V aktualizovaném kódu není vlákno pracující na snídani blokováno při čekání na jakýkoli spuštěný úkol, který není dokončen. U některých aplikací je tato změna vše, co potřebujete. Aplikaci můžete povolit, aby podporovala interakci uživatelů při stahování dat z webu. V jiných scénářích můžete chtít spustit jiné úkoly při čekání na dokončení předchozího úkolu.

Souběžné spouštění úloh

U většiny operací chcete okamžitě spustit několik nezávislých úloh. Po dokončení každého úkolu zahájíte další práci, která je připravená začít. Když použijete tuto metodologii na příklad snídaně, můžete si snídani připravit rychleji. Získáte také všechno připravené blízko ke stejné době, takže si můžete vychutnat horkou snídani.

Třídy System.Threading.Tasks.Task a související typy jsou třídy, které můžete použít k použití tohoto stylu odůvodnění u probíhajících úkolů. Tento přístup umožňuje psát kód, který se více podobá způsobu, jakým vytváříte snídani v reálném životě. Začnete vařit vejce, slaninu a toast ve stejnou dobu. Vzhledem k tomu, že každá položka jídla vyžaduje akci, obrátíte pozornost na tento úkol, postaráte se o danou akci a pak počkáte na něco jiného, co vyžaduje vaši pozornost.

Ve vašem kódu spustíte úlohu a podržíte objekt Task, který zastupuje tuto úlohu. Metodu await použijete ke zpoždění zahájení práce na úkolu, dokud nebude výsledek hotov.

Tyto změny použijte u kódu snídaně. Prvním krokem je uložení úloh pro operace při jejich spuštění místo použití výrazu 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!");

Tyto revize nepomáhají připravit snídani rychleji. Výraz await se použije u všech úkolů hned po spuštění. Dalším krokem je přesunutí výrazů await pro slaninu a vejce na konec postupu před podáváním snídaně.

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

Teď máte asynchronně připravenou snídani, která trvá asi 20 minut, než se připraví. Celková doba vaření je snížena, protože některé úlohy běží souběžně.

Diagram, který znázorňuje pokyny pro přípravu snídaně jako osm asynchronních úkolů, které se dokončí asi za 20 minut, přičemž bohužel vejce a slanina se spálí.

Kód aktualizace zlepšují proces přípravy snížením doby vaření, ale způsobují regresi tím, že spálí vejce a slaninu. Spustíte všechny asynchronní úlohy najednou. Na každý úkol čekáte jenom v případě, že potřebujete výsledky. Kód může být podobný programu ve webové aplikaci, která odesílá požadavky na různé mikroslužby a pak kombinuje výsledky do jedné stránky. Všechny požadavky provedete okamžitě a potom použijete výraz await na všechny tyto úkoly a vytvoříte webovou stránku.

Kompozice úkolů

Předchozí revize kódu pomáhají připravit všechno na snídani ve stejnou dobu, kromě toastu. Proces vytváření toastu je složení asynchronní operace (opékání chleba) se synchronními operacemi (namazání másla a džemu na toast). Tento příklad ukazuje důležitý koncept asynchronního programování:

Důležitý

Složení asynchronní operace následované synchronní prací je asynchronní operace. Uvedli jsme jiný způsob, pokud je některá část operace asynchronní, je celá operace asynchronní.

V předchozích aktualizacích jste zjistili, jak používat Task nebo Task<TResult> objekty k uložení spuštěných úloh. Než použijete jeho výsledek, počkáte na každý úkol. Dalším krokem je vytvoření metod, které představují kombinaci jiné práce. Než podáte snídani, chcete počkat na úkol, který představuje opékání chleba, než na něj natřete máslo a marmeládu.

Tuto práci můžete vyjádřit následujícím kódem:

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

    return toast;
}

Metoda MakeToastWithButterAndJamAsync má v podpisu modifikátor async, který signalizuje kompilátoru, že metoda obsahuje výraz await a obsahuje asynchronní operace. Metoda představuje úkol, který opéká chleba, poté roztírá máslo a džem. Metoda vrátí Task<TResult> objekt, který představuje složení tří operací.

Upravený hlavní blok kódu teď vypadá takto:

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

Tato změna kódu znázorňuje důležitou techniku pro práci s asynchronním kódem. Úkoly vytvoříte oddělením operací do nové metody, která vrací úkol. Můžete zvolit, kdy na tento úkol počkat. Další úlohy můžete spouštět souběžně.

Zpracování asynchronních výjimek

Do tohoto okamžiku váš kód implicitně předpokládá, že všechny úkoly byly úspěšně dokončeny. Asynchronní metody vyvolává výjimky, stejně jako jejich synchronní protějšky. Cíle asynchronní podpory pro výjimky a zpracování chyb jsou stejné jako pro asynchronní podporu obecně. Osvědčeným postupem je napsat kód, který čte jako řadu synchronních příkazů. Úkoly vyvolají výjimky, když se nemohou úspěšně dokončit. Klientský kód může tyto výjimky zachytit při použití výrazu await na spuštěnou úlohu.

V příkladu se snídaní předpokládejme, že se toustovač vznítí při opékání chleba. Tento problém můžete simulovat tak, že upravíte metodu ToastBreadAsync tak, aby odpovídala následujícímu kódu:

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

Poznámka

Při kompilaci tohoto kódu se zobrazí upozornění na nedostupný kód. Tato chyba je navržená. Jakmile toustovač zachytí oheň, operace nepokračuje normálně a kód vrátí chybu.

Po provedení změn kódu spusťte aplikaci a zkontrolujte výstup:

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)

Všimněte si, že poměrně mnoho úkolů se dokončí mezi okamžikem, kdy toustovač začne hořet, a chvílí, kdy systém zjistí výjimku. Pokud úloha, která běží asynchronně vyvolá výjimku, je tento úkol chybný. Objekt Task obsahuje výjimku vyvolanou ve vlastnosti Task.Exception. Chybné úlohy vyvolají výjimku při použití výrazu await na úlohu.

Existují dva důležité mechanismy pro pochopení tohoto procesu:

  • Jak je výjimka uložená v chybné úloze
  • Jak se výjimka dekomprimuje a znovu vyvolá, když kód čeká (await) na chybném úkolu

Pokud kód spuštěný asynchronně vyvolá výjimku, je výjimka uložena v Task objektu. Vlastnost Task.Exception je objekt System.AggregateException, protože během asynchronní práce může být vyvolány více než jedna výjimka. Jakákoli vyvolaná výjimka se přidá do kolekce AggregateException.InnerExceptions. Pokud je vlastnost Exception null, vytvoří se nový objekt AggregateException a vyvolaná výjimka se stane první položkou v kolekci.

Nejběžnějším scénářem chybné úlohy je, že vlastnost Exception obsahuje přesně jednu výjimku. Když váš kód čeká na chybnou úlohu, znovu vyvolá první AggregateException.InnerExceptions výjimku v kolekci. Tento výsledek je důvodem, proč výstup z příkladu zobrazuje objekt System.InvalidOperationException místo objektu AggregateException. Extrahování první vnitřní výjimky činí práci s asynchronními metodami co nejpodobnější práci s jejich synchronními protějšky. Vlastnost Exception v kódu můžete prozkoumat, když váš scénář může vygenerovat více výjimek.

Rada

Doporučeným postupem je, aby všechny výjimky ověřování argumentů vznikly synchronně z metod vracejících úlohy. Další informace a příklady naleznete v tématu Výjimky v metodách vracejících úlohy.

Než budete pokračovat k další části, zakomentujte následující dva příkazy ve své metodě ToastBreadAsync. Nechcete spustit další oheň:

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

Efektivní použití výrazů await u úkolů

Řadu výrazů await můžete vylepšit na konci předchozího kódu pomocí metod Task třídy. Jedno rozhraní API je metoda WhenAll, která vrací objekt Task, který se dokončí, když jsou dokončeny všechny úkoly v seznamu argumentů. Následující kód ukazuje tuto metodu:

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

Další možností je použít metodu WhenAny, která vrátí Task<Task> objekt, který se dokončí při dokončení některého z jejích argumentů. Na vrácený úkol můžete počkat, protože víte, že se úkol dokončil. Následující kód ukazuje, jak můžete pomocí metody WhenAny počkat na dokončení prvního úkolu a pak zpracovat jeho výsledek. Po zpracování výsledku z dokončeného úkolu odeberete dokončený úkol ze seznamu úkolů předaných do WhenAny metody.

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

Na konci fragmentu kódu si všimněte výrazu await finishedTask;. Výraz await Task.WhenAny nečeká na dokončenou úlohu, ale počká na objekt Task vrácený metodou Task.WhenAny. Výsledkem Task.WhenAny metody je dokončená (nebo chybná) úloha. Osvědčeným postupem je počkat na úkol znovu, i když víte, že je úkol dokončený. Tímto způsobem můžete načíst výsledek úkolu, nebo zajistit, aby byla vyvolána jakákoli výjimka, která způsobí selhání úkolu.

Kontrola konečného kódu

Finální verze kódu vypadá takto:

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

Kód dokončí asynchronní úlohy související se snídaní přibližně za 15 minut. Celková doba se sníží, protože některé úlohy běží souběžně. Kód současně monitoruje více úloh a provádí akce pouze podle potřeby.

diagram, který ukazuje pokyny pro přípravu snídaně jako šest asynchronních úloh, které se dokončí přibližně za 15 minut, a kód monitoruje možné přerušení.

Konečný kód je asynchronní. Přesněji odráží, jak může člověk vařit snídani. Porovnejte konečný kód s první ukázkou kódu v článku. Základní akce jsou stále jasné čtením kódu. Konečný kód si můžete přečíst stejným způsobem jako v seznamu pokynů pro vytvoření snídaně, jak je znázorněno na začátku článku. Jazykové funkce pro klíčová slova async a await pomáhají při překladu, který každý člověk provádí podle psaných pokynů: Zahajte úkoly, jakmile můžete, a nezůstávejte zablokovaní, zatímco čekáte na dokončení úkolů.

Další krok