Programowanie asynchroniczne przy użyciu elementów async i await

Model programowania asynchronicznego zadań (TAP) zapewnia abstrakcję kodu asynchronicznego. Kod jest pisany jako sekwencja instrukcji, podobnie jak zawsze. Możesz odczytać ten kod tak, jakby każda instrukcja kończyła się przed następnym rozpoczęciem. Kompilator wykonuje wiele przekształceń, ponieważ niektóre z tych instrukcji mogą rozpocząć pracę i zwrócić wartość , Task która reprezentuje bieżącą pracę.

Jest to cel tej składni: włącz kod, który odczytuje jak sekwencję instrukcji, ale jest wykonywany w znacznie bardziej skomplikowanej kolejności na podstawie alokacji zasobów zewnętrznych i po zakończeniu zadań. Jest to analogiczne do sposobu, w jaki ludzie dają instrukcje dotyczące procesów, które obejmują zadania asynchroniczne. W tym artykule użyjesz przykładowych instrukcji dotyczących programowania, aby zobaczyć, jak słowa kluczowe i ułatwiają uzasadnienie kodu, który zawiera serię instrukcji async await asynchronicznych. Napiszesz instrukcje podobne do poniższej listy, aby wyjaśnić, jak zrobić sobie najgorętszą instrukcje:

  1. Posypać kubek kawy.
  2. Rozgrzać miszkę, a następnie przegrzać dwie części.
  3. Przeszukaj trzy wycinki boczek.
  4. Podsuń dwie części bread(Wyskakujące).
  5. Dodaj do wyskakującego powiadomienia i zacięcie.
  6. Zjedz kielicha pomarańczowego sosu.

Jeśli masz doświadczenie w pracy z programem , wykonasz te instrukcje asynchronicznie. Należy zacząć rozgrzewkę dla nasypów, a następnie uruchomić boczek. W tosterze umieścisz bułę, a następnie zaczniesz nagłośliwą. Na każdym etapie procesu należy rozpocząć zadanie, a następnie zwrócić uwagę na zadania, które są gotowe do uwagi.

Dobrą praktyką jest praca asynchroniczna, która nie jest równoległa. Jedna osoba (lub wątek) może obsłużyć wszystkie te zadania. Kontynuując analogię zjechaną, jedna osoba może zrobić asynchronicznie, rozpoczynając następne zadanie przed ukończeniem pierwszego. Postęp chyli się nad tym, czy ktoś je obserwuje. Gdy tylko zaczniesz rozgrzewkę dla ćwy, możesz zacząć ćwyć boczek. Po zakończeniu bokonu możesz umieścić w tosterze danie.

W przypadku algorytmu równoległego potrzebujesz wielu kuchaczy (lub wątków). Jeden z nich to jeden z bekonów i tak dalej. Każdy z nich koncentruje się tylko na tym jednym zadaniu. Każdy kuchar (lub wątek) zostałby zablokowany synchronicznie, czekając, aż bocon będzie gotowy do przerzucania, lub wyskakujący do okienka podręcznego.

Teraz rozważ te same instrukcje zapisane jako instrukcje języka C#:

using System;
using System.Threading.Tasks;

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

synchroniczny

Synchronicznie przygotowany program zajęł około 30 minut, ponieważ suma jest sumą poszczególnych zadań.

Uwaga

Klasy Coffee , , , i są Egg Bacon Toast Juice puste. Są one po prostu klasami znaczników na potrzeby pokazu, nie zawierają żadnych właściwości i nie służą do żadnego innego celu.

Komputery nie interpretują tych instrukcji tak samo jak ludzie. Komputer będzie blokować każdą z instrukcji, dopóki praca nie zostanie ukończona przed przejściem do następnej instrukcji. Powoduje to niezadowolenie. Późniejsze zadania nie zostaną uruchomione, dopóki wcześniejsze zadania nie zostaną ukończone. Utworzenie najgorętszego zamówienia trwałoby znacznie dłużej, a niektóre elementy zostałyby zimne przed podaniem.

Jeśli chcesz, aby komputer wykonywał powyższe instrukcje asynchronicznie, musisz napisać kod asynchroniczny.

Te kwestie są ważne dla programów, które piszesz dzisiaj. Podczas pisania programów klienckich chcesz, aby interfejs użytkownika reagował na dane wejściowe użytkownika. Aplikacja nie powinna sprawić, że telefon będzie wyświetlany jako zamrożony podczas pobierania danych z Internetu. Podczas pisania programów serwerowych nie chcesz blokować wątków. Te wątki mogą służyć do obsługi innych żądań. Użycie kodu synchronicznego, gdy istnieją asynchroniczne alternatywy, ma zaniedbywną możliwość tańszego skalowania w zewnątrz. Płacisz za te zablokowane wątki.

Udane nowoczesne aplikacje wymagają kodu asynchronicznego. Bez obsługi języka pisanie kodu asynchronicznego wymagało wywołań zwrotnych, zdarzeń ukończenia lub innych środków, które przesłaniały pierwotną intencję kodu. Zaletą kodu synchronicznego jest to, że jego akcje krok po kroku ułatwiają skanowanie i zrozumienie. Tradycyjne modele asynchroniczne wymuszają skupienie się na asynchronicznym charakterze kodu, a nie na podstawowych działaniach kodu.

Zamiast tego nie blokuj , await

Poprzedni kod demonstruje złe rozwiązanie: konstruowanie kodu synchronicznego w celu wykonywania operacji asynchronicznych. Jak napisano, ten kod blokuje wątek wykonujący go, aby nie wykonywał żadnej innej pracy. Nie zostanie ona przerwana, gdy żadne z zadań jest w toku. Wyglądałoby to tak, jakby po włozieniu do tostera wsadł do niego bułę. Zignorujesz wszystkie osoby, które rozmawiają z Toastem, aż do wyskakującego powiadomienia.

Zacznijmy od zaktualizowania tego kodu, aby wątek nie blokował się podczas wykonywania zadań. Słowo kluczowe zapewnia nieblokowanie sposobu uruchamiania zadania, a następnie kontynuowania await wykonywania po zakończeniu tego zadania. Prosta asynchroniczna wersja kodu "make a przegotowy" będzie wyglądać podobnie do następującego fragmentu kodu:

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

Ważne

Łączny czas, który upłynął, jest w przybliżeniu taki sam jak początkowa wersja synchroniczna. Kod nie musi jeszcze korzystać z niektórych kluczowych funkcji programowania asynchronicznego.

Porada

Treści metod obiektów , i zostały zaktualizowane w celu FryEggsAsync FryBaconAsync ToastBreadAsync zwrócenia Task<Egg> odpowiednio , Task<Bacon> i Task<Toast> . Nazwy metod są zmieniane z ich oryginalnej wersji, tak aby zawierały sufiks "Async". Ich implementacje są wyświetlane jako część ostatniej wersji w dalszej części tego artykułu.

Ten kod nie blokuje czasu, gdy chciwy lub bocon jest przechwycony. Ten kod nie uruchamia jednak żadnych innych zadań. Nadal możesz umieścić wyskakujące okienko w wyskakującym okienku i zaglądać do niego, aż pojawi się. Ale przynajmniej należy odpowiedzieć na wszystkie osoby, które chciały Twoją uwagę. W restauracji, w której składanych jest wiele zamówień, kucharka może rozpocząć kolejne przyjadło, gdy pierwszy jest chłoniak.

Teraz wątek pracujący nad sygotą nie jest blokowany podczas czekania na wszystkie uruchomione zadania, które jeszcze nie zostały ukończone. W przypadku niektórych aplikacji ta zmiana jest wszystkim, co jest potrzebne. Aplikacja z graficznym interfejsem użytkownika nadal odpowiada użytkownikowi tylko za pomocą tej zmiany. Jednak w tym scenariuszu potrzebujesz więcej. Nie chcesz, aby każde zadanie składnika było wykonywane sekwencyjnie. Lepiej jest uruchomić każde z zadań składowych przed oczekiwanie na ukończenie poprzedniego zadania.

Współbieżne uruchamianie zadań

W wielu scenariuszach chcesz natychmiast uruchomić kilka niezależnych zadań. Następnie, po zakończeniu każdego zadania, możesz kontynuować inną pracę, która jest gotowa. W analogicznej analogii pozwala to szybciej uzyskać zmiełtną pracę. Wszystko jest wykonywane blisko tego samego czasu. Otrzymasz gorącą nagorę.

Typy System.Threading.Tasks.Task powiązane i są klasami, których można użyć do wytłaniania informacji o zadaniach, które są w toku. Dzięki temu możesz napisać kod, który bardziej przypomina sposób, w jaki faktycznie tworzysz szemrę. W tym samym czasie zaczniesz od bekonu i wyskakującego powiadomienia. Ponieważ każda z nich wymaga działania, należy zwrócić uwagę na to zadanie, zająć się następną akcją, a następnie poczekać na coś innego, co wymaga Twojej uwagi.

Należy uruchomić zadanie i przytrzymać obiekt Task reprezentujący pracę. Każde zadanie await należy przed rozpoczęciem pracy z jego wynikiem.

Dokonajmy tych zmian w kodzie źródłowym. Pierwszym krokiem jest przechowywanie zadań dla operacji podczas ich uruchamiania, a nie oczekiwanie na nie:

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

Następnie możesz przenieść instrukcje dla boczeka i na koniec metody, przed await rozpoczęciem obsługi:

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

asynchroniczne asynchroniczne

Asynchronicznie przygotowana operacja zajęła około 20 minut, a tym razem oszczędność czasu wynika z tego, że niektóre zadania są uruchamiane współbieżnie.

Powyższy kod działa lepiej. Wszystkie zadania asynchroniczne są uruchamiane jednocześnie. Każde zadanie należy czekać tylko wtedy, gdy będą potrzebne wyniki. Poprzedni kod może być podobny do kodu w aplikacji internetowej, który wykonuje żądania różnych mikrousług, a następnie łączy wyniki w jedną stronę. Natychmiast wyślisz wszystkie żądania, a następnie wszystkie await te zadania i skomponuj stronę internetową.

Kompozycja z zadaniami

Wszystko jest gotowe do najechańsze w tym samym czasie z wyjątkiem wyskakującego powiadomienia. Tworzenie wyskakującego powiadomienia to kompozycja operacji asynchronicznej (wyskakującej nasypki z powiadomieniami o godzinie) i operacji synchronicznych (dodawanie cyja i jama). Zaktualizowanie tego kodu ilustruje ważną koncepcję:

Ważne

Kompozycja operacji asynchronicznej, po której następuje praca synchroniczna, jest operacją asynchroniczną. Określono inny sposób: jeśli jakakolwiek część operacji jest asynchroniczna, cała operacja jest asynchroniczna.

Powyższy kod pokazuje, że można używać obiektów lub do Task Task<TResult> przechowywania uruchomionych zadań. Każde await zadanie należy przed użyciem jego wyniku. Następnym krokiem jest utworzenie metod reprezentujących kombinację innych prac. Przed dodaniem potraw chcesz poczekać na zadanie, które reprezentuje toast zajechanie do papieru przed dodaniem dań i zaciemnień. Można to reprezentować za pomocą następującego kodu:

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

    return toast;
}

Poprzednia metoda ma async w podpisie modyfikator . Sygnalizuje kompilatorowi, że ta metoda await zawiera instrukcje ; zawiera operacje asynchroniczne. Ta metoda reprezentuje zadanie, które podsumuje bułę, a następnie dodaje go i jam. Ta metoda zwraca Task<TResult> wartość , która reprezentuje kompozycję tych trzech operacji. Główny blok kodu staje się teraz:

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

Poprzednia zmiana zilustrowała ważną technikę pracy z kodem asynchronicznym. Zadania są redagowanie przez podzielenie operacji na nową metodę, która zwraca zadanie. Możesz wybrać, kiedy należy czekać na to zadanie. Możesz uruchamiać inne zadania współbieżnie.

Wyjątki asynchroniczne

Do tego momentu zakładano niejawnie, że wszystkie te zadania zostały ukończone pomyślnie. Metody asynchroniczne zgłaszanie wyjątków, podobnie jak ich synchroniczne odpowiedniki. Asynchroniczna obsługa wyjątków i obsługi błędów dąży do tych samych celów co obsługa asynchroniczna ogólnie: należy napisać kod, który odczytuje jak serię instrukcji synchronicznych. Zadania zgłaszają wyjątki, gdy nie mogą zakończyć się pomyślnie. Kod klienta może przechwyć te wyjątki, gdy uruchomione zadanie to awaited . Załóżmy na przykład, że toster chwytuje pożar podczas tworzenia wyskakującego powiadomienia. Można to zasymulować, ToastBreadAsync modyfikując metodę tak, aby dopasować ją do następującego kodu:

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

Uwaga

Podczas kompilowania poprzedniego kodu otrzymasz ostrzeżenie dotyczące nieosiąganego kodu. Jest to zamierzone, ponieważ gdy toster chwyci pożar, operacje nie będą kontynuowane normalnie.

Po wymuseniu tych zmian uruchom aplikację. Dane wyjściowe będą podobne do następującego tekstu:

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)

Zwróć uwagę, że między pożarem tostera a zaobserwowaniem wyjątku jest wykonywanych kilka zadań. Gdy zadanie, które jest uruchamiane asynchronicznie, zgłasza wyjątek, to zadanie ma błąd. Obiekt Task zawiera wyjątek zgłoszony we właściwości Task.Exception . Zadania z błędami zgłaszają wyjątek, gdy są oczekujące.

Istnieją dwa ważne mechanizmy, które należy zrozumieć: jak wyjątek jest przechowywany w uszkodzonym zadaniu oraz jak wyjątek jest rozpakowywany i ponownie występuje, gdy kod oczekuje na uszkodzone zadanie.

Gdy kod uruchomiony asynchronicznie zgłasza wyjątek, ten wyjątek jest przechowywany w Task . Właściwość jest elementem , ponieważ podczas pracy asynchronicznej Task.Exception System.AggregateException może być zgłaszany więcej niż jeden wyjątek. Wszystkie zgłoszone wyjątki są dodawane do AggregateException.InnerExceptions kolekcji. Jeśli ta właściwość ma wartość null, tworzona jest nowa właściwość, Exception a zgłoszony AggregateException wyjątek jest pierwszym elementem w kolekcji.

Najbardziej powszechnym scenariuszem błędnych zadań jest to, że Exception właściwość zawiera dokładnie jeden wyjątek. Gdy awaits koduje uszkodzone zadanie, pierwszy wyjątek w kolekcji AggregateException.InnerExceptions jest ponownie cytujący. Dlatego dane wyjściowe z tego przykładu pokazują element zamiast InvalidOperationException AggregateException . Wyodrębnienie pierwszego wyjątku wewnętrznego sprawia, że praca z metodami asynchronicznmi jest jak najbardziej podobna do pracy z ich synchronicznymi odpowiednikami. Właściwość w kodzie Exception można sprawdzić, gdy scenariusz może generować wiele wyjątków.

Przed rozpoczęciem wyeksmentuj te dwa wiersze w ToastBreadAsync metodzie jako komentarz. Nie chcesz uruchamiać kolejnego pożaru:

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

Wydajne oczekiwanie na zadania

Serię instrukcji na końcu poprzedniego kodu można ulepszyć przy await użyciu metod Task klasy . Jeden z tych interfejsów API to , który zwraca wartość , która jest zwracana po ukończeniu wszystkich zadań na liście argumentów, jak pokazano w WhenAll Task poniższym kodzie:

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

Inną opcją jest użycie funkcji , która zwraca wartość , która kończy się po WhenAny Task<Task> zakończeniu któregokolwiek z jego argumentów. Możesz poczekać na zwrócone zadanie, wiedząc, że zostało ono już zakończone. Poniższy kod pokazuje, jak można użyć funkcji , aby poczekać na zakończenie pierwszego zadania, WhenAny a następnie przetworzyć jego wynik. Po przetworzeniu wyniku ukończonego zadania należy usunąć to ukończone zadanie z listy zadań przekazanych do 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");
    }
    breakfastTasks.Remove(finishedTask);
}

Po tych wszystkich zmianach ostateczna wersja kodu wygląda następująco:

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

namespace AsyncBreakfast
{
    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");
                }
                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();
        }
    }
}

gdy dowolna asynchroniczna synchronizacja

Ostatnia wersja asynchronicznie przygotowanej szemerki trwała około 15 minut, ponieważ niektóre zadania były uruchamiane współbieżnie, a kod monitorował wiele zadań jednocześnie i podjął akcję tylko wtedy, gdy było to potrzebne.

Ten końcowy kod jest asynchroniczny. Dokładniej odzwierciedla to sposób, w jaki osoba będzie chłonić nasadę. Porównaj poprzedni kod z pierwszym przykładowym kodem w tym artykule. Podstawowe akcje nadal nie są wczytywają kodu. Możesz przeczytać ten kod w taki sam sposób, w jaki będziesz czytać te instrukcje dotyczące tworzenia chłoniaka na początku tego artykułu. Funkcje językowe programu i zapewniają tłumaczenie, które każda osoba wykonuje, aby postępować zgodnie z tymi zapisanymi instrukcjami: uruchamiać zadania zgodnie z możliwościami i nie blokować oczekiwania async await na ukończenie zadań.

Następne kroki