Asynchrone Programmierung mit async und awaitAsynchronous programming with async and await

Das aufgabenbasierte asynchrone Programmiermodell stellt eine Abstraktion über asynchronen Code bereit.The Task asynchronous programming model (TAP) provides an abstraction over asynchronous code. Sie können Code in gewohnter Weise als eine Folge von Anweisungen schreiben.You write code as a sequence of statements, just like always. Sie können diesen Code so lesen, als ob jede Anweisung abgeschlossen wäre, bevor die nächste Anweisung beginnt.You can read that code as though each statement completes before the next begins. Der Compiler führt eine Reihe von Transformationen durch, da möglicherweise einige dieser Anweisungen gestartet werden und eine Task zurückgeben, die die derzeit ausgeführte Arbeit darstellt.The compiler performs a number of transformations because some of those statements may start work and return a Task that represents the ongoing work.

Dies ist das Ziel dieser Syntax: Code zu aktivieren, der sich wie eine Folge von Anweisungen liest, aber in einer deutlich komplizierteren Reihenfolge ausgeführt wird, die auf einer externen Ressourcenzuordnung und dem Abschluss von Aufgaben basiert.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. Vergleichbar ist dies mit der Art und Weise, wie Menschen Anweisungen für Prozesse erteilen, die asynchrone Aufgaben enthalten.It's analogous to how people give instructions for processes that include asynchronous tasks. In diesem Artikel verwenden Sie als Beispiel Anweisungen für die Zubereitung eines Frühstücks, um zu lernen, wie die Schlüsselwörter async und await die Analyse von Code erleichtern, der eine Reihe asynchroner Anweisungen enthält.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. Um die Zubereitung eines Frühstücks zu erläutern, würden Sie Anweisungen schreiben, und Ihre Liste sähe ungefähr so aus:You'd write the instructions something like the following list to explain how to make a breakfast:

  1. Schenken Sie sich eine Tasse Kaffee ein.Pour a cup of coffee.
  2. Erhitzen Sie eine Pfanne, und braten Sie darin zwei Eier.Heat up a pan, then fry two eggs.
  3. Braten Sie drei Scheiben Frühstücksspeck.Fry three slices of bacon.
  4. Toasten Sie zwei Scheiben Brot.Toast two pieces of bread.
  5. Bestreichen Sie das getoastete Brot mit Butter und Marmelade.Add butter and jam to the toast.
  6. Schenken Sie sich ein Glas Orangensaft ein.Pour a glass of orange juice.

Wenn Sie über Erfahrung im Kochen verfügen, würden Sie diese Anweisungen asynchron ausführen.If you have experience with cooking, you'd execute those instructions asynchronously. Sie würden zunächst die Pfanne für die Eier erhitzen und dann mit dem Frühstücksspeck beginnen.You'd start warming the pan for eggs, then start the bacon. Sie würden das Brot in den Toaster stecken und danach mit den Eiern beginnen.You'd put the bread in the toaster, then start the eggs. Bei jedem Schritt des Prozesses würden Sie eine Aufgabe starten und dann Ihre Aufmerksamkeit auf Aufgaben lenken, die für Ihre Aufmerksamkeit bereit sind.At each step of the process, you'd start a task, then turn your attention to tasks that are ready for your attention.

Die Zubereitung eines Frühstücks ist ein gutes Beispiel für asynchrone Arbeiten, die nicht parallel ausgeführt werden.Cooking breakfast is a good example of asynchronous work that isn't parallel. Eine Person (oder ein Thread) kann alle diese Aufgaben erledigen.One person (or thread) can handle all these tasks. Um beim Beispiel des Frühstücks zu bleiben: Eine Person kann das Frühstück asynchron zubereiten, indem sie die nächste Aufgabe startet, bevor die erste Aufgabe abgeschlossen ist.Continuing the breakfast analogy, one person can make breakfast asynchronously by starting the next task before the first completes. Die Zubereitung schreitet voran, und zwar unabhängig davon, ob jemand eine Auge darauf hat oder nicht.The cooking progresses whether or not someone is watching it. Sobald Sie damit beginnen, die Pfanne für die Eier zu erhitzen, können Sie mit dem Braten des Frühstücksspecks beginnen.As soon as you start warming the pan for the eggs, you can begin frying the bacon. Nachdem Sie das Braten des Frühstücksspecks begonnen haben, können Sie das Brot in den Toaster stecken.Once the bacon starts, you can put the bread into the toaster.

Für einen parallelen Algorithmus bräuchten Sie mehrere Köche (bzw. Threads).For a parallel algorithm, you'd need multiple cooks (or threads). Ein Koch würde sich um die Eier kümmern, ein weiterer um den Frühstücksspeck usw.One would make the eggs, one the bacon, and so on. Jeder Koch würde sich nur auf diese eine Aufgabe konzentrieren.Each one would be focused on just that one task. Jeder Koch (bzw. Thread) würde synchron blockiert, während er darauf wartet, dass der Frühstücksspeck gewendet werden muss oder der Toaster das Brot auswirft.Each cook (or thread) would be blocked synchronously waiting for bacon to be ready to flip, or the toast to pop.

Sehen Sie sich nun dieselben Anweisungen als C#-Anweisungen an:Now, consider those same instructions written as C# statements:

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

Hinweis

Die Klassen Coffee, Egg, Bacon, Toast und Juice sind leer.The Coffee, Egg, Bacon, Toast, and Juice classes are empty. Sie sind lediglich Markerklassen für Demonstrationszwecke und enthalten keine Eigenschaften.They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.

Computer interpretieren diese Anweisungen anders als Menschen.Computers don't interpret those instructions the same way people do. Nach jeder Anweisung blockiert der Computer das weitere Vorgehen, bis die Arbeit abgeschlossen ist. Erst danach fährt er mit der nächsten Anweisung fort.The computer will block on each statement until the work is complete before moving on to the next statement. So käme kein schmackhaftes Frühstück zustande.That creates an unsatisfying breakfast. Die späteren Aufgaben würden erst gestartet, wenn die früheren Aufgaben abgeschlossen sind.The later tasks wouldn't be started until the earlier tasks had completed. Die Zubereitung des Frühstücks würde wesentlich länger dauern, und einige Komponenten wären bereits wieder kalt, bis sie serviert werden.It would take much longer to create the breakfast, and some items would have gotten cold before being served.

Wenn der Computer die obigen Anweisungen asynchron ausführen soll, müssen Sie asynchronen Code schreiben.If you want the computer to execute the above instructions asynchronously, you must write asynchronous code.

Diese Überlegungen sind wichtig für die Programme, die Sie heutzutage schreiben.These concerns are important for the programs you write today. Wenn Sie Client-Programme schreiben, möchten Sie, dass die Benutzeroberfläche auf Benutzereingaben reagiert.When you write client programs, you want the UI to be responsive to user input. Ihre Anwendung sollte nicht den Eindruck erwecken, dass sich das Smartphone aufgehängt hat, während es Daten aus dem Web herunterlädt.Your application shouldn't make a phone appear frozen while it's downloading data from the web. Wenn Sie Serverprogramme schreiben, möchten Sie nicht, dass Threads blockiert werden.When you write server programs, you don't want threads blocked. Diese Threads könnten für andere Anforderungen benötigt werden.Those threads could be serving other requests. Die Verwendung von synchronem Code, wenn asynchrone Alternativen vorhanden sind, beeinträchtigt Ihre Möglichkeiten für günstigere Erweiterungen.Using synchronous code when asynchronous alternatives exist hurts your ability to scale out less expensively. Sie bezahlen für die blockierten Threads.You pay for those blocked threads.

Erfolgreiche moderne Anwendungen erfordern asynchronen Code.Successful modern applications require asynchronous code. Ohne Sprachunterstützung erforderte das Schreiben von asynchronem Code Rückrufe, Abschlussereignisse oder andere Methoden, die die ursprüngliche Absicht des Codes verdeckten.Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. Synchroner Code hat den Vorteil, dass er einfach zu verstehen ist.The advantage of the synchronous code is that it's easy to understand. Dank der schrittweise durchgeführten Aktionen lässt sich der Code leicht überprüfen und nachvollziehen.The step-by-step actions make it easy to scan and understand. Bei traditionellen asynchronen Modellen mussten Sie sich auf die asynchronen Eigenschaften des Codes und nicht auf die grundlegenden Aktionen des Codes konzentrieren.Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code.

Nicht blockieren, stattdessen „await“ verwendenDon't block, await instead

Der obige Code zeigt eine schlechte Praxis: das Erstellen von synchronem Code zum Ausführen asynchroner Vorgänge.The preceding code demonstrates a bad practice: constructing synchronous code to perform asynchronous operations. In der vorliegenden Form hindert dieser Code den Thread an der Ausführung aller anderen Arbeiten.As written, this code blocks the thread executing it from doing any other work. Es wird nicht unterbrochen, während eine der anderen Aufgaben ausgeführt wird.It won't be interrupted while any of the tasks are in progress. Dies wäre so, als würden Sie den Toaster anstarren, nachdem Sie das Brot hineingesteckt haben.It would be as though you stared at the toaster after putting the bread in. Sie wären so lange für niemanden ansprechbar, bis das getoastete Brot ausgeworfen wurde.You'd ignore anyone talking to you until the toast popped.

Aktualisieren wir also diesen Code so, dass der Thread nicht blockiert wird, während Aufgaben ausgeführt werden.Let's start by updating this code so that the thread doesn't block while tasks are running. Das Schlüsselwort await bietet die Möglichkeit, eine Aufgabe zu starten und dann die Ausführung fortzusetzen, wenn diese Aufgabe abgeschlossen ist, ohne dass es dabei zu einer Blockierung kommt.The await keyword provides a non-blocking way to start a task, then continue execution when that task completes. Eine einfache asynchrone Version des Codes für die Frühstückszubereitung sähe daher wie der folgende Codeausschnitt aus: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 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!");
}

Tipp

Die Methodenkörper von FryEggsAsync, FryBaconAsync und ToastBreadAsync wurden aktualisiert, sodass sie jetzt Task<Egg>, Task<Bacon> und Task<Toast> zurückgeben.The method bodies of the FryEggsAsync, FryBaconAsync, and ToastBreadAsync have all been updated to return Task<Egg>, Task<Bacon>, and Task<Toast> respectively. Die Methoden werden umbenannt und enthalten dann das Suffix „Async“.The methods are renamed from their original version to include the "Async" suffix. Ihre Implementierungen werden als Teil der endgültigen Version weiter unten in diesem Artikel gezeigt.Their implementations are shown as part of the final version later in this article.

Dieser Code verursacht keine Blockierung, während die Eier oder der Frühstücksspeck gebraten werden.This code doesn't block while the eggs or the bacon are cooking. Dieser Code startet jedoch keine anderen Aufgaben.This code won't start any other tasks though. Sie würden weiterhin das Brot in den Toaster stecken und das Gerät so lange anstarren, bis das Brot ausgeworfen wird.You'd still put the toast in the toaster and stare at it until it pops. Aber Sie wären zumindest für andere Personen ansprechbar, die Ihre Aufmerksamkeit wünschen.But at least, you'd respond to anyone that wanted your attention. In einem Restaurant, in dem mehrere Bestellungen aufgegeben werden, könnte der Koch mit der Zubereitung eines weiteren Frühstücks beginnen, während das erste Frühstück zubereitet wird.In a restaurant where multiple orders are placed, the cook could start another breakfast while the first is cooking.

Jetzt wird der Thread für die Frühstückszubereitung nicht blockiert, während er auf gestartete Aufgaben wartet, die noch nicht abgeschlossen sind.Now, the thread working on the breakfast isn't blocked while awaiting any started task that hasn't yet finished. Bei einigen Anwendungen reicht diese Änderung bereits aus.For some applications, this change is all that's needed. Alleine diese Änderung führt bereits dazu, dass eine GUI-Anwendung weiterhin auf den Benutzer reagiert.A GUI application still responds to the user with just this change. In diesem Szenario möchten Sie jedoch mehr.However, for this scenario, you want more. Die einzelnen Komponenten bzw. Aufgaben sollen nicht sequenziell ausgeführt werden.You don't want each of the component tasks to be executed sequentially. Es ist besser, die einzelnen Komponenten/Aufgaben zu starten, ohne auf den Abschluss der vorherigen Aufgabe zu warten.It's better to start each of the component tasks before awaiting the previous task's completion.

Aufgaben gleichzeitig startenStart tasks concurrently

In vielen Szenarios möchten Sie mehrere voneinander unabhängige Aufgaben unverzüglich starten.In many scenarios, you want to start several independent tasks immediately. Sobald eine der Aufgabe abgeschlossen ist, können Sie dann andere Aufgaben fortsetzen, die bereit sind.Then, as each task finishes, you can continue other work that's ready. Um beim Beispiel des Frühstücks zu bleiben, würden Sie dieses sehr viel schneller zubereiten können.In the breakfast analogy, that's how you get breakfast done more quickly. Außerdem können Sie alle Aufgaben nahezu gleichzeitig fertigstellen.You also get everything done close to the same time. Und erhalten so ein warmes Frühstück.You'll get a hot breakfast.

System.Threading.Tasks.Task und verwandte Typen sind Klassen, mit denen Sie Aufgaben analysieren können, die gerade ausgeführt werden.The System.Threading.Tasks.Task and related types are classes you can use to reason about tasks that are in progress. So können Sie Code schreiben, der sehr viel stärker der Art und Weise ähnelt, wie Sie wirklich ein Frühstück zubereiten.That enables you to write code that more closely resembles the way you'd actually create breakfast. Sie würden gleichzeitig mit der Zubereitung von Eiern, Frühstücksspeck und Toast beginnen.You'd start cooking the eggs, bacon, and toast at the same time. Da hierfür jeweils eine Aktion erforderlich ist, würden Sie Ihre Aufmerksamkeit auf diese Aufgabe lenken, sich um die nächste Aktion kümmern und dann auf etwas anderes warten, das Ihre Aufmerksamkeit erfordert.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.

Sie starten eine Aufgabe und behalten dann das Task-Objekt bei, das die Arbeit repräsentiert.You start a task and hold on to the Task object that represents the work. Sie warten jede Aufgabe ab („await“), bevor Sie mit ihrem Ergebnis arbeiten.You'll await each task before working with its result.

Lassen Sie uns nun die entsprechenden Änderungen an dem Code für das Frühstück vornehmen.Let's make these changes to the breakfast code. Der erste Schritt besteht darin, die Aufgaben für Vorgänge bei deren Start zu speichern, anstatt auf sie zu warten: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 = 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!");

Als Nächstes können Sie die await-Anweisungen für den Frühstücksspeck und die Eier an das Ende der Methode, vor dem Servieren des Frühstücks, verschieben: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 = 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!");

Der obige Code funktioniert besser.The preceding code works better. Sie starten alle asynchronen Aufgaben gleichzeitig.You start all the asynchronous tasks at once. Sie verwenden „await“ nur für Aufgaben, wenn Sie deren Ergebnisse benötigen.You await each task only when you need the results. Der obige Code ähnelt beispielsweise Code in einer Webanwendung, die verschiedene Microservices anfordert und dann die Ergebnisse auf einer einzigen Seite zusammenfasst.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. Sie führen alle Anforderungen sofort aus, warten dann aber mit await auf alle diese Aufgaben und stellen die Webseite zusammen.You'll make all the requests immediately, then await all those tasks and compose the web page.

Kombination mit AufgabenComposition with tasks

Sie haben alles, was zum Frühstück benötigt wird, gleichzeitig fertig, mit Ausnahme des Toasts.You have everything ready for breakfast at the same time except the toast. Die Zubereitung des Toasts ist eine Kombination aus einem asynchronen Vorgang (das Toasten des Brotes) und synchronen Vorgängen (das Bestreichen mit Butter und Marmelade).Making the toast is the composition of an asynchronous operation (toasting the bread), and synchronous operations (adding the butter and the jam). Die Aktualisierung dieses Codes veranschaulicht ein wichtiges Konzept:Updating this code illustrates an important concept:

Wichtig

Die Kombination aus einem asynchronen Vorgang gefolgt von einer synchronen Tätigkeit ergibt einen asynchronen Vorgang.The composition of an asynchronous operation followed by synchronous work is an asynchronous operation. Mit anderen Worten: Wenn ein Teil eines Vorgangs asynchron ist, ist der gesamte Vorgang asynchron.Stated another way, if any portion of an operation is asynchronous, the entire operation is asynchronous.

Der obige Code zeigt, dass Sie Task- oder Task<TResult>-Objekte verwenden können, um laufende Aufgaben beizubehalten.The preceding code showed you that you can use Task or Task<TResult> objects to hold running tasks. Sie warten mit „await“ auf jede Aufgabe, bevor Sie deren Ergebnis verwenden.You await each task before using its result. Der nächste Schritt besteht im Erstellen von Methoden, die die Kombination anderer Tätigkeiten darstellen.The next step is to create methods that represent the combination of other work. Bevor Sie das Frühstück servieren, möchten Sie auf die Aufgabe warten, die für das Toasten des Brotes steht, bevor Sie das getoastete Brot mit Butter und Marmelade bestreichen.Before serving breakfast, you want to await the task that represents toasting the bread before adding butter and jam. Dies können Sie mit dem folgenden Code darstellen:You can represent that work with the following code:

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

    return toast;
}

Die obige Methode verfügt in ihrer Signatur über den Modifizierer async.The preceding method has the async modifier in its signature. Dies signalisiert dem Compiler, dass diese Methode eine await-Anweisung enthält. Sie enthält also asynchrone Vorgänge.That signals to the compiler that this method contains an await statement; it contains asynchronous operations. Diese Methode steht für die Aufgabe, bei der das Brot getoastet und dann mit Butter und Marmelade bestrichen wird.This method represents the task that toasts the bread, then adds butter and jam. Diese Methode gibt das Ergebnis Task<TResult> aus, d. h. die Kombination dieser drei Vorgänge.This method returns a Task<TResult> that represents the composition of those three operations. Der Hauptcodeblock sieht jetzt wie folgt aus: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!");
}

Die obige Änderung veranschaulicht eine wichtige Technik für das Arbeiten mit asynchronem Code.The previous change illustrated an important technique for working with asynchronous code. Sie erstellen Aufgaben, indem Sie die Vorgänge in eine neue Methode unterteilen, die eine Aufgabe zurückgibt.You compose tasks by separating the operations into a new method that returns a task. Sie können entscheiden, wann auf diese Aufgabe gewartet werden soll.You can choose when to await that task. Sie können andere Aufgaben gleichzeitig starten.You can start other tasks concurrently.

Effizient auf Aufgaben wartenAwait tasks efficiently

Die Reihe der await-Anweisungen am Ende des obigen Codes kann mithilfe der Methoden der Task-Klasse verbessert werden.The series of await statements at the end of the preceding code can be improved by using methods of the Task class. Eine dieser APIs ist WhenAll, sie gibt eine Task zurück, die abgeschlossen wird, wenn alle Aufgaben in ihrer Argumentenliste abgeschlossen sind. Dies zeigt der folgende Code: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!");

Eine weitere Möglichkeit ist die Verwendung von WhenAny. Dies gibt eine Task<Task> zurück, die abgeschlossen wird, wenn eines ihrer Argumente abgeschlossen wird.Another option is to use WhenAny, which returns a Task<Task> that completes when any of its arguments completes. Sie können mit „await“ auf die zurückgegebene Aufgabe warten – in dem Wissen, dass sie bereits abgeschlossen ist.You can await the returned task, knowing that it has already finished. Der folgende Code zeigt, wie Sie WhenAny verwenden können, um mit „await“ auf den Abschluss der ersten Aufgabe zu warten und dann deren Ergebnis zu verarbeiten.The following code shows how you could use WhenAny to await the first task to finish and then process its result. Nach dem Verarbeiten des Ergebnisses der abgeschlossenen Aufgabe können Sie diese abgeschlossene Aufgabe aus der Liste der an WhenAny übergebenen Aufgaben entfernen.After processing the result from the completed task, you remove that completed task from the list of tasks passed to 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);
}

Nach allen diesen Änderungen sieht der endgültige Code folgendermaßen aus: After all those changes, the final version of the code looks like this:

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

Dieser letzte Code ist asynchron.This final code is asynchronous. Er spiegelt genauer wieder, wie ein Mensch ein Frühstück zubereiten würde.It more accurately reflects how a person would cook a breakfast. Vergleichen Sie den obigen Code mit dem ersten Codebeispiel in diesem Artikel.Compare the preceding code with the first code sample in this article. Die Kernaktionen sind beim Lesen des Codes noch immer deutlich erkennbar.The core actions are still clear from reading the code. Sie können diesen Code in derselben Weise lesen wie die Anweisungen für die Zubereitung eines Frühstücks am Anfang dieses Artikels.You can read this code the same way you'd read those instructions for making a breakfast at the beginning of this article. Die Sprachfunktionen für async und await stellen die Übersetzung bereit, die jede Person vornimmt, um diese schriftlichen Anweisungen zu befolgen: Starten Sie Aufgaben, sobald Sie dies können, und blockieren Sie nicht den weiteren Fortgang, indem Sie auf den Abschluss von Aufgaben warten.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.

Nächste SchritteNext steps