Programowanie asynchroniczne z Async i awaitAsynchronous programming with async and await

Model programowania asynchronicznego zadania (TAP) zawiera streszczenie w porównaniu z kodem asynchronicznym.The Task asynchronous programming model (TAP) provides an abstraction over asynchronous code. Napiszesz kod jako sekwencję instrukcji, tak jak zawsze.You write code as a sequence of statements, just like always. Można odczytać ten kod, tak jakby każda instrukcja została zakończona przed rozpoczęciem następnego.You can read that code as though each statement completes before the next begins. Kompilator wykonuje szereg transformacji, ponieważ niektóre z tych instrukcji mogą zacząć pracę i zwracać Task, które reprezentują bieżącą pracę.The compiler performs a number of transformations because some of those statements may start work and return a Task that represents the ongoing work.

Jest to cel tej składni: Włącz kod, który odczytuje się jak sekwencja instrukcji, ale wykonuje się w znacznie bardziej skomplikowanej kolejności na podstawie zewnętrznej alokacji zasobów i po zakończeniu zadań.That's the goal of this syntax: enable code that reads like a sequence of statements, but executes in a much more complicated order based on external resource allocation and when tasks complete. Jest to analogiczne do sposobu, w jaki ludzie udostępniają instrukcje dotyczące procesów zawierających zadania asynchroniczne.It's analogous to how people give instructions for processes that include asynchronous tasks. W tym artykule przedstawiono przykład instrukcji służących do nawiązywania śniadania, aby zobaczyć, jak async i await słowa kluczowe ułatwiają powody dotyczące kodu, który zawiera szereg instrukcji asynchronicznych.Throughout this article, you'll use an example of instructions for making a breakfast to see how the async and await keywords make it easier to reason about code that includes a series of asynchronous instructions. Napiszesz instrukcje podobne do poniższej listy, aby wyjaśnić, jak utworzyć śniadanie:You'd write the instructions something like the following list to explain how to make a breakfast:

  1. Wlać filiżankę kawy.Pour a cup of coffee.
  2. Podgrzewaj panoramę, a następnie przefrj dwie jaja.Heat up a pan, then fry two eggs.
  3. Narybka trzy wycinki Bacon.Fry three slices of bacon.
  4. Wyskakujące dwa elementy chleba.Toast two pieces of bread.
  5. Dodaj masło i dżem do wyskakującego powiadomienia.Add butter and jam to the toast.
  6. Wlać szklany sok pomarańczowy.Pour a glass of orange juice.

W przypadku korzystania z funkcji gotowania należy wykonać te instrukcje asynchronicznie.If you have experience with cooking, you'd execute those instructions asynchronously. Zaczniesz rozgrzewać panoramy dla jaj, a następnie uruchomić Bacon.You'd start warming the pan for eggs, then start the bacon. Należy umieścić chleb w wyskakującym, a następnie rozpocząć jaja.You'd put the bread in the toaster, then start the eggs. W każdym kroku procesu należy uruchomić zadanie, a następnie zwrócić uwagę na zadania, które są gotowe do Twojej uwagi.At each step of the process, you'd start a task, then turn your attention to tasks that are ready for your attention.

Śniadanie gotowania jest dobrym przykładem asynchronicznej pracy, która nie jest równoległa.Cooking breakfast is a good example of asynchronous work that isn't parallel. Jedna osoba (lub wątek) może obsłużyć wszystkie te zadania.One person (or thread) can handle all these tasks. W przypadku kontynuowania nieprzerwanego śniadania jedna osoba może napełnić proces, rozpoczynając następne zadanie przed pierwszym zakończeniem.Continuing the breakfast analogy, one person can make breakfast asynchronously by starting the next task before the first completes. Postęp gotowania niezależnie od tego, czy ktoś ją ogląda.The cooking progresses whether or not someone is watching it. Gdy tylko zaczniesz rozgrzewać kadrowanie dla jaj, możesz zacząć Frying Bacon.As soon as you start warming the pan for the eggs, you can begin frying the bacon. Po rozpoczęciu Bacon można umieścić chleb w wyskakującym pasku.Once the bacon starts, you can put the bread into the toaster.

Dla algorytmu równoległego potrzebna jest wiele Cooka (lub wątków).For a parallel algorithm, you'd need multiple cooks (or threads). Jedna z nich spowoduje, że jaja, jeden z Bacon i tak dalej.One would make the eggs, one the bacon, and so on. Każdy z nich będzie koncentrować się tylko na jednym zadaniu.Each one would be focused on just that one task. Każdy Cooka (lub wątek) zostałby zablokowany synchronicznie, oczekując na Bacon w celu przerzucenia lub wyskakującego okienka.Each cook (or thread) would be blocked synchronously waiting for bacon to be ready to flip, or the toast to pop.

Teraz Rozważ te same instrukcje, które zostały C# wpisane jako instrukcje:Now, consider those same instructions written as C# statements:

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

Komputery nie interpretują tych instrukcji w taki sam sposób jak ludzie.Computers don't interpret those instructions the same way people do. Komputer będzie blokować każdą instrukcję do momentu zakończenia pracy przed przejściem do następnej instrukcji.The computer will block on each statement until the work is complete before moving on to the next statement. Spowoduje to utworzenie niespełniającego śniadania.That creates an unsatisfying breakfast. Późniejsze zadania nie zostaną uruchomione do momentu ukończenia wcześniejszych zadań.The later tasks wouldn't be started until the earlier tasks had completed. Utworzenie śniadania może zająć dużo czasu, a niektóre elementy byłyby na zimno przed rozpoczęciem.It would take much longer to create the breakfast, and some items would have gotten cold before being served.

Jeśli chcesz, aby komputer wykonywał powyższe instrukcje asynchronicznie, musisz napisać kod asynchroniczny.If you want the computer to execute the above instructions asynchronously, you must write asynchronous code.

Te zagadnienia są ważne w przypadku programów, które można napisać dzisiaj.These concerns are important for the programs you write today. Podczas pisania programów klienckich, interfejs użytkownika ma reagować na dane wejściowe użytkownika.When you write client programs, you want the UI to be responsive to user input. Twoja aplikacja nie powinna sprawiać, że telefon jest zamrożony podczas pobierania danych z sieci Web.Your application shouldn't make a phone appear frozen while it's downloading data from the web. W przypadku pisania programów serwerowych nie ma możliwości blokowania wątków.When you write server programs, you don't want threads blocked. Te wątki mogą obsługiwać inne żądania.Those threads could be serving other requests. Użycie kodu synchronicznego, gdy istnieje asynchroniczna alternatywa, powoduje, że skalowanie w poziomie jest tańsze.Using synchronous code when asynchronous alternatives exist hurts your ability to scale out less expensively. Płacisz za te zablokowane wątki.You pay for those blocked threads.

Pomyślne aplikacje Modern wymagają kodu asynchronicznego.Successful modern applications require asynchronous code. Bez obsługi języka, pisania asynchronicznie wymaganego kodu wywołania zwrotne, zdarzenia ukończenia lub inne oznacza, że zasłania pierwotny cel kodu.Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. Zaletą kodu synchronicznego jest łatwość zrozumienia.The advantage of the synchronous code is that it's easy to understand. Akcje krok po kroku ułatwiają skanowanie i zrozumienie.The step-by-step actions make it easy to scan and understand. Tradycyjne modele asynchroniczne zmuszają do skoncentrowania się na asynchronicznym charakterze kodu, a nie na podstawowych działaniach kodu.Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code.

Nie blokuj, await zamiastDon't block, await instead

Powyższy kod demonstruje niewłaściwe rozwiązanie: konstruowanie kodu synchronicznego do wykonywania operacji asynchronicznych.The preceding code demonstrates a bad practice: constructing synchronous code to perform asynchronous operations. Zgodnie z zapisaniem ten kod blokuje wątek wykonujący go przez wykonywanie innych czynności.As written, this code blocks the thread executing it from doing any other work. Nie zostanie ona przerwana, gdy jakieś zadania są w toku.It won't be interrupted while any of the tasks are in progress. Po wprowadzeniu chleba na wyskakującym pasku jest to możliwe.It would be as though you stared at the toaster after putting the bread in. Dowiesz się, jak wszyscy rozmawiają z Twoim zdjęte.You'd ignore anyone talking to you until the toast popped.

Zacznijmy od zaktualizowania tego kodu, aby wątek nie zablokuje wykonywania zadań.Let's start by updating this code so that the thread doesn't block while tasks are running. Słowo kluczowe await zapewnia nieblokujący sposób uruchamiania zadania, a następnie kontynuuje wykonywanie po zakończeniu zadania.The await keyword provides a non-blocking way to start a task, then continue execution when that task completes. Prosta, asynchroniczna wersja kodu naśniadania będzie wyglądać następująco:A simple asynchronous version of the make a breakfast code would look like the following snippet:

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");
    Egg eggs = await FryEggs(2);
    Console.WriteLine("eggs are ready");
    Bacon bacon = await FryBacon(3);
    Console.WriteLine("bacon is ready");
    Toast toast = await ToastBread(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");

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

Ten kod nie jest blokowany, gdy jaja lub Bacon są gotowania.This code doesn't block while the eggs or the bacon are cooking. W tym kodzie nie zostaną uruchomione żadne inne zadania.This code won't start any other tasks though. Nadal wyskakujące powiadomienie na wyskakującym pasku i stare je do momentu pop.You'd still put the toast in the toaster and stare at it until it pops. Ale co najmniej, wystarczy odpowiedzieć na każdą z nich.But at least, you'd respond to anyone that wanted your attention. W restauracji, w której znajduje się wiele zamówień, Cooka może rozpocząć kolejną śniadanie, podczas gdy pierwszy jest gotowania.In a restaurant where multiple orders are placed, the cook could start another breakfast while the first is cooking.

Teraz wątek pracujący nad śniadaniem nie jest blokowany podczas oczekiwania na zadanie uruchomione, które jeszcze się nie zakończyło.Now, the thread working on the breakfast isn't blocked while awaiting any started task that hasn't yet finished. W przypadku niektórych aplikacji ta zmiana jest wymagana.For some applications, this change is all that's needed. Aplikacja GUI nadal reaguje na użytkownika za pomocą tylko tej zmiany.A GUI application still responds to the user with just this change. Jednak w tym scenariuszu potrzebujesz więcej informacji.However, for this scenario, you want more. Nie chcesz, aby poszczególne zadania składników były wykonywane sekwencyjnie.You don't want each of the component tasks to be executed sequentially. Przed zaczekaniem na ukończenie poprzedniego zadania lepiej uruchomić wszystkie zadania składników.It's better to start each of the component tasks before awaiting the previous task's completion.

Uruchom zadania współbieżnieStart tasks concurrently

W wielu scenariuszach należy od razu zacząć uruchamiać kilka niezależnych zadań.In many scenarios, you want to start several independent tasks immediately. Następnie, po zakończeniu każdego zadania, można kontynuować wykonywanie innych gotowych zadań.Then, as each task finishes, you can continue other work that's ready. W przypadku korzystania z analogu śniadaniowego wiesz, jak szybko uzyskać śniadanie.In the breakfast analogy, that's how you get breakfast done more quickly. Wszystko to jest również wykonywane blisko tego samego czasu.You also get everything done close to the same time. Uzyskasz gorącą śniadanie.You'll get a hot breakfast.

System.Threading.Tasks.Task i powiązane typy są klasami, których można użyć, aby przyczynić się do zadań, które są w toku.The System.Threading.Tasks.Task and related types are classes you can use to reason about tasks that are in progress. Dzięki temu można napisać kod, który jest bardziej zbliżony do sposobu, w jaki faktycznie tworzysz śniadanie.That enables you to write code that more closely resembles the way you'd actually create breakfast. W tym samym czasie można rozpocząć gotowanie jaj, Bacon i wyskakujących powiadomień.You'd start cooking the eggs, bacon, and toast at the same time. Ponieważ każda z nich wymaga działania, należy zwrócić uwagę na to zadanie, zadbać o następną akcję, a następnie oczekiwać na coś innego, co wymaga uwagi.As each requires action, you'd turn your attention to that task, take care of the next action, then await for something else that requires your attention.

Rozpoczęcie zadania i przytrzymanie do obiektu Task, który reprezentuje pracę.You start a task and hold on to the Task object that represents the work. Każde zadanie zostanie await przed rozpoczęciem pracy z jego wynikiem.You'll await each task before working with its result.

Wprowadźmy te zmiany w kodzie śniadania.Let's make these changes to the breakfast code. Pierwszym krokiem jest przechowywanie zadań do operacji po ich uruchomieniu, a nie ich oczekiwanie:The first step is to store the tasks for operations when they start, rather than awaiting them:

Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Task<Egg> eggsTask = FryEggs(2);
Egg eggs = await eggsTask;
Console.WriteLine("eggs are ready");
Task<Bacon> baconTask = FryBacon(3);
Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");
Task<Toast> toastTask = ToastBread(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żna przenieść instrukcje await dla Bacon i jaj do końca metody przed obsłużeniem śniadania:Next, you can move the await statements for the bacon and eggs to the end of the method, before serving breakfast:

Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Task<Egg> eggsTask = FryEggs(2);
Task<Bacon> baconTask = FryBacon(3);
Task<Toast> toastTask = ToastBread(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!");

Poprzedni kod działa lepiej.The preceding code works better. Wszystkie zadania asynchroniczne są uruchamiane jednocześnie.You start all the asynchronous tasks at once. Każde zadanie zostanie zaczekane tylko wtedy, gdy są potrzebne wyniki.You await each task only when you need the results. Poprzedni kod może być podobny do kodu w aplikacji sieci Web, która wysyła żądania różnych mikrousług, a następnie łączy wyniki w jedną stronę.The preceding code may be similar to code in a web application that makes requests of different microservices, then combines the results into a single page. Natychmiast wprowadzisz wszystkie żądania, a następnie await wszystkie te zadania i utworzysz stronę sieci Web.You'll make all the requests immediately, then await all those tasks and compose the web page.

Kompozycja z zadaniamiComposition with tasks

Wszystko jest gotowe do śniadania w tym samym czasie, z wyjątkiem wyskakującego powiadomienia.You have everything ready for breakfast at the same time except the toast. Wyskakujące powiadomienia są kompozycją operacji asynchronicznej (wyskakujące chleb) i synchronicznymi operacjami (dodaniem masła i zakleszczeniem).Making the toast is the composition of an asynchronous operation (toasting the bread), and synchronous operations (adding the butter and the jam). Aktualizacja tego kodu ilustruje ważną koncepcję:Updating this code illustrates an important concept:

Ważne

Kompozycja operacji asynchronicznej, po której następuje synchroniczna operacja, jest operacją asynchroniczną.The composition of an asynchronous operation followed by synchronous work is an asynchronous operation. Podano inny sposób, jeśli jakakolwiek część operacji jest asynchroniczna, cała operacja jest asynchroniczna.Stated another way, if any portion of an operation is asynchronous, the entire operation is asynchronous.

Powyższy kod wskazuje, że można użyć obiektów Task lub Task<TResult> do przechowywania uruchomionych zadań.The preceding code showed you that you can use Task or Task<TResult> objects to hold running tasks. Każde zadanie zostanie await przed użyciem jego wyniku.You await each task before using its result. Następnym krokiem jest utworzenie metod, które reprezentują kombinację innych zadań.The next step is to create methods that represent the combination of other work. Przed objęciem śniadania, należy oczekiwać, że zadanie reprezentuje wyskakujące chleb przed dodaniem masła i dżemu.Before serving breakfast, you want to await the task that represents toasting the bread before adding butter and jam. Można przedstawić, że pracujesz z poniższym kodem:You can represent that work with the following code:

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

Poprzednia metoda ma modyfikator async w jego podpisie.The preceding method has the async modifier in its signature. Sygnalizuje kompilatorowi, że ta metoda zawiera instrukcję await; zawiera operacje asynchroniczne.That signals to the compiler that this method contains an await statement; it contains asynchronous operations. Ta metoda przedstawia zadanie, które wyskakujące chleb, a następnie dodaje masło i dżem.This method represents the task that toasts the bread, then adds butter and jam. Ta metoda zwraca Task<TResult>, który reprezentuje skład tych trzech operacji.This method returns a Task<TResult> that represents the composition of those three operations. Główny blok kodu jest teraz:The main block of code now becomes:

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

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

Poprzednia zmiana ilustruje ważną technikę pracy z kodem asynchronicznym.The previous change illustrated an important technique for working with asynchronous code. Tworzysz zadania, oddzielając operacje do nowej metody, która zwraca zadanie.You compose tasks by separating the operations into a new method that returns a task. Możesz określić, kiedy ma oczekiwać to zadanie.You can choose when to await that task. Można uruchamiać inne zadania współbieżnie.You can start other tasks concurrently.

Efektywne zadania oczekująceAwait tasks efficiently

Serie await instrukcji na końcu poprzedzającego kodu można ulepszyć przy użyciu metod klasy Task.The series of await statements at the end of the preceding code can be improved by using methods of the Task class. Jeden z tych interfejsów API jest WhenAll, który zwraca Task, który kończy się po zakończeniu wszystkich zadań na liście argumentów, jak pokazano w poniższym kodzie:One of those APIs is WhenAll, which returns a Task that completes when all the tasks in its argument list have completed, as shown in the following code:

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

Innym rozwiązaniem jest użycie WhenAny, która zwraca Task<Task>, który kończy się po zakończeniu któregokolwiek z argumentów.Another option is to use WhenAny, which returns a Task<Task> that completes when any of its arguments completes. Możesz oczekiwać na zwrócone zadanie, wiedząc, że już zostało zakończone.You can await the returned task, knowing that it has already finished. Poniższy kod pokazuje, jak można użyć WhenAny do oczekiwania na zakończenie pierwszego zadania, a następnie przetworzyć jego wynik.The following code shows how you could use WhenAny to await the first task to finish and then process its result. Po przetworzeniu wyniku z zadania zakończonego zadanie zostało usunięte z listy zadań przesłanych do WhenAny.After processing the result from the completed task, you remove that completed task from the list of tasks passed to WhenAny.

var allTasks = new List<Task>{eggsTask, baconTask, toastTask};
while (allTasks.Any())
{
    Task finished = await Task.WhenAny(allTasks);
    if (finished == eggsTask)
    {
        Console.WriteLine("eggs are ready");
    }
    else if (finished == baconTask)
    {
        Console.WriteLine("bacon is ready");
    }
    else if (finished == toastTask)
    {
        Console.WriteLine("toast is ready");
    }
    allTasks.Remove(finished);
}
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");

Po wprowadzeniu wszystkich zmian końcowa wersja Main wyglądać podobnie do następującego kodu:After all those changes, the final version of Main looks like the following code:

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 allTasks = new List<Task>{eggsTask, baconTask, toastTask};
    while (allTasks.Any())
    {
        Task finished = await Task.WhenAny(allTasks);
        if (finished == eggsTask)
        {
            Console.WriteLine("eggs are ready");
        }
        else if (finished == baconTask)
        {
            Console.WriteLine("bacon is ready");
        }
        else if (finished == toastTask)
        {
            Console.WriteLine("toast is ready");
        }
        allTasks.Remove(finished);
    }
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");

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

Ten ostatni kod jest asynchroniczny.This final code is asynchronous. Dokładniej odzwierciedla to, w jaki sposób osoba może zacooka śniadanie.It more accurately reflects how a person would cook a breakfast. Porównaj poprzedni kod z pierwszym przykładowym kodem w tym artykule.Compare the preceding code with the first code sample in this article. Podstawowe działania są nadal jasne przed odczytaniem kodu.The core actions are still clear from reading the code. Można odczytać ten kod w taki sam sposób, jak w przypadku uzyskania śniadania na początku tego artykułu.You can read this code the same way you'd read those instructions for making a breakfast at the beginning of this article. Funkcje języka dla async i await zapewniają tłumaczenie dla każdej osoby, która wykonuje następujące instrukcje: Rozpocznij zadania w miarę możliwości i nie blokuj oczekiwania na ukończenie zadań.The language features for async and await provide the translation every person makes to follow those written instructions: start tasks as you can and don't block waiting for tasks to complete.