使用 async 和 await 進行非同步程式設計Asynchronous programming with async and await

非同步程式設計模型(點按)工作會提供非同步程式碼的抽象概念。The Task asynchronous programming model (TAP) provides an abstraction over asynchronous code. 您可以和往常一樣,將程式碼撰寫成一連串的陳述式。You write code as a sequence of statements, just like always. 您可以將該程式碼讀成每個陳述式會先完成,再開始下一個陳述式。You can read that code as though each statement completes before the next begins. 編譯器會執行一些轉換,因為部分陳述式可能會開始工作,並傳回表示進行工作的 TaskThe compiler performs a number of transformations because some of those statements may start work and return a Task that represents the ongoing work.

這是此語法的目標:讓程式碼讀起來就像是一連串的陳述式,但會根據外部資源配置和工作完成時間,以比較複雜的順序來執行。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. 這類似於人員為包含非同步工作之程序提供指示的方式。It's analogous to how people give instructions for processes that include asynchronous tasks. 在本文中,您將使用一段指示來進行早餐,以瞭解 asyncawait 關鍵字如何讓程式碼更容易理解,包括一系列的非同步指令。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. 您撰寫了類似下列清單的指示,來說明如何準備早餐:You'd write the instructions something like the following list to explain how to make a breakfast:

  1. 倒杯咖啡。Pour a cup of coffee.
  2. 熱鍋,然後煎兩顆蛋。Heat up a pan, then fry two eggs.
  3. 煎三片培根。Fry three slices of bacon.
  4. 烤兩片吐司。Toast two pieces of bread.
  5. 在吐司塗上奶油和果醬。Add butter and jam to the toast.
  6. 倒杯柳橙汁。Pour a glass of orange juice.

如果您有烹飪經驗,您會非同步地執行這些指示。If you have experience with cooking, you'd execute those instructions asynchronously. 您會從為雞蛋熱鍋開始,然後開始煎培根。You'd start warming the pan for eggs, then start the bacon. 等到將麵包放入烤麵包機,再開始煎蛋。You'd put the bread in the toaster, then start the eggs. 在程序的每個步驟,您會開始一個工作,然後將注意轉移到其他需要您注意的工作。At each step of the process, you'd start a task, then turn your attention to tasks that are ready for your attention.

準備早餐很適合用來示範非平行的非同步工作。Cooking breakfast is a good example of asynchronous work that isn't parallel. 一個人 (或執行緒) 可處理所有這些工作。One person (or thread) can handle all these tasks. 繼續以早餐為例,一個人可能會以非同步方式準備早餐,不等到第一個工作完成,就開始下一個工作。Continuing the breakfast analogy, one person can make breakfast asynchronously by starting the next task before the first completes. 不論是否有旁觀者,烹飪都會進行。The cooking progresses whether or not someone is watching it. 開始為雞蛋熱鍋之後,您可以開始煎培根。As soon as you start warming the pan for the eggs, you can begin frying the bacon. 等到開始煎培根,您可以將麵包放入烤麵包機。Once the bacon starts, you can put the bread into the toaster.

若要進行平行演算法,您需要多個廚師 (或執行緒)。For a parallel algorithm, you'd need multiple cooks (or threads). 一個人會負責煎蛋、一個人會負責煎培根,依此類推。One would make the eggs, one the bacon, and so on. 每個人只會專注於一個工作。Each one would be focused on just that one task. 每個廚師 (或執行緒) 禁止以同步方式等候培根準備好翻面,或吐司彈出。Each cook (or thread) would be blocked synchronously waiting for bacon to be ready to flip, or the toast to pop.

現在,考慮將這些相同指示撰寫成 C# 陳述式: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();
        }
    }
}

同步早餐

同步準備的早餐大約需要30分鐘的時間,因為總計是每個個別工作的總和。The synchronously prepared breakfast, took roughly 30 minutes because the total is the sum of each individual task.

注意

CoffeeEggBaconToastJuice 類別是空的。The Coffee, Egg, Bacon, Toast, and Juice classes are empty. 它們只是用來示範的標記類別,不包含任何屬性,也不提供其他用途。They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.

電腦解譯這些指示的方式與人類不同。Computers don't interpret those instructions the same way people do. 電腦會封鎖每個陳述式,直到工作完成為止,再繼續下一個陳述式。The computer will block on each statement until the work is complete before moving on to the next statement. 這會導致早餐無法令人滿意。That creates an unsatisfying breakfast. 後續工作必須等到先前工作完成才會開始。The later tasks wouldn't be started until the earlier tasks had completed. 這會花更長的時間來準備早餐,且在上菜前,有些菜可能會變涼。It would take much longer to create the breakfast, and some items would have gotten cold before being served.

如果您想要電腦以非同步方式執行上述指示,您必須撰寫非同步程式碼。If you want the computer to execute the above instructions asynchronously, you must write asynchronous code.

這些考量對您現今撰寫的程式很重要。These concerns are important for the programs you write today. 當您撰寫用戶端程式時,您想要 UI 可以回應使用者輸入。When you write client programs, you want the UI to be responsive to user input. 您的應用程式不應該讓手機在從網路下載資料時呈現凍結。Your application shouldn't make a phone appear frozen while it's downloading data from the web. 當您撰寫伺服器程式時,您不想要執行緒被封鎖。When you write server programs, you don't want threads blocked. 這些執行緒可能會服務其他要求。Those threads could be serving other requests. 在存在替代的非同步程式碼時使用同步程式碼,會導致您無法以較不耗費成本的方式擴充。Using synchronous code when asynchronous alternatives exist hurts your ability to scale out less expensively. 這些封鎖的執行緒會耗費成本。You pay for those blocked threads.

現代化應用程式需要非同步程式碼才能成功。Successful modern applications require asynchronous code. 由於沒有語言支援,撰寫非同步程式碼需要回呼、完成事件,或隱藏程式碼原本意圖的其他方式。Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. 同步程式碼的優點是容易了解。The advantage of the synchronous code is that it's easy to understand. 逐步動作讓您可以輕鬆地瀏覽與了解。The step-by-step actions make it easy to scan and understand. 傳統非同步模型迫使您將重點放在程式碼的非同步本質,而不是程式碼的基本動作。Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code.

不要封鎖,而是等候Don't block, await instead

上述程式碼示範不正確的做法:建構同步程式碼來執行非同步作業。The preceding code demonstrates a bad practice: constructing synchronous code to perform asynchronous operations. 如內容所指,此程式碼會防止執行緒執行任何其他工作。As written, this code blocks the thread executing it from doing any other work. 在任何工作進行時,它不會遭到中斷。It won't be interrupted while any of the tasks are in progress. 就像是將麵包放入烤麵包機之後,直盯著烤麵包機。It would be as though you stared at the toaster after putting the bread in. 在吐司彈出之前,您不會理會任何人對您說的話。You'd ignore anyone talking to you until the toast popped.

我們將從更新此程式碼開始,讓執行緒在工作執行時不會遭到封鎖。Let's start by updating this code so that the thread doesn't block while tasks are running. await 關鍵字提供一個非封鎖方式來開始工作,然後在工作完成時繼續執行。The await keyword provides a non-blocking way to start a task, then continue execution when that task completes. 準備早餐程式碼的一個簡單非同步版本看起來像下列程式碼片段: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!");
}

重要

總經過時間大致上與初始 synchonous 版本相同。The total elapsed time is roughly the same as the initial synchonous version. 程式碼尚未利用非同步程式設計的一些主要功能。The code has yet to take advantage of some of the key features of asynchronous programming.

提示

、和的方法主體 FryEggsAsync FryBaconAsync ToastBreadAsync 全都已更新為 Task<Egg> 分別傳回、 Task<Bacon>Task<Toast>The method bodies of the FryEggsAsync, FryBaconAsync, and ToastBreadAsync have all been updated to return Task<Egg>, Task<Bacon>, and Task<Toast> respectively. 方法會從其原始版本重新命名,以包含 "Async" 尾碼。The methods are renamed from their original version to include the "Async" suffix. 其執行方式會顯示為本文稍後的最終版本的一部分。Their implementations are shown as part of the final version later in this article.

此程式碼不會在煎蛋或煎培根時封鎖其他工作。This code doesn't block while the eggs or the bacon are cooking. 但此程式碼也不會開始任何其他工作。This code won't start any other tasks though. 您仍會將吐司放入烤麵包機,並在彈出前直盯著它瞧。You'd still put the toast in the toaster and stare at it until it pops. 但至少,您會回應任何需要您注意的人。But at least, you'd respond to anyone that wanted your attention. 在點了多份早餐的餐廳中,廚師可能會在第一份早餐還在準備時,就開始準備另一份早餐。In a restaurant where multiple orders are placed, the cook could start another breakfast while the first is cooking.

現在,準備早餐的執行緒在等候任何已開始但尚未完成的工作時,不會遭到封鎖。Now, the thread working on the breakfast isn't blocked while awaiting any started task that hasn't yet finished. 對於某些應用程式而言,只需要這項變更。For some applications, this change is all that's needed. GUI 應用程式仍會只以這項變更來回應使用者。A GUI application still responds to the user with just this change. 不過在此案例中,您需要不只一項變更。However, for this scenario, you want more. 您不想要循序執行每個元件工作。You don't want each of the component tasks to be executed sequentially. 較好的做法是開始每個元件工作,然後等候先前的工作完成。It's better to start each of the component tasks before awaiting the previous task's completion.

同時開始工作Start tasks concurrently

在許多情況下,您想要立即開始數個獨立工作。In many scenarios, you want to start several independent tasks immediately. 然後,在每個工作完成時,您可以繼續其他準備好的工作。Then, as each task finishes, you can continue other work that's ready. 以早餐為例,這會讓您更快速地完成早餐。In the breakfast analogy, that's how you get breakfast done more quickly. 您也會幾乎同時完成所有工作。You also get everything done close to the same time. 因此,您會有熱騰騰的早餐。You'll get a hot breakfast.

System.Threading.Tasks.Task 和相關類型是您可以用來理解進行中工作的類別。The System.Threading.Tasks.Task and related types are classes you can use to reason about tasks that are in progress. 這可讓您撰寫出更類似您實際準備早餐方式的程式碼。That enables you to write code that more closely resembles the way you'd actually create breakfast. 您會同時開始煎蛋、煎培根和烤吐司。You'd start cooking the eggs, bacon, and toast at the same time. 由於每個工作都需要執行動作,因此您會將注意轉移到該工作、處理下一個動作,然後等候其他需要您注意的工作。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.

您會開始一個工作,並保存表示該工作的 Task 物件。You start a task and hold on to the Task object that represents the work. 您會 await 每個工作,再處理其結果。You'll await each task before working with its result.

我們將對早餐程式碼進行這些變更。Let's make these changes to the breakfast code. 第一個步驟是為作業儲存開始時的工作,而不是等候工作: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!");

接下來,您可以將培根與雞蛋的 await 陳述式移至方法結尾,並在供應早餐之前: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!");

非同步早餐

非同步備妥的早餐大約需要20分鐘的時間,這是因為某些工作可以同時執行。The asynchronously prepared breakfast took roughly 20 minutes, this is because some tasks were able to run concurrently.

上述程式碼的效果更好。The preceding code works better. 您會同時開始所有非同步工作。You start all the asynchronous tasks at once. 只有需要結果時,才會等候每個工作。You await each task only when you need the results. 上述程式碼可能會類似於提出不同微服務要求,然後將結果合併成單一頁面的 Web 應用程式程式碼。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. 您會立即提出所有要求,然後 await 所有這些工作並撰寫網頁。You'll make all the requests immediately, then await all those tasks and compose the web page.

工作組合Composition with tasks

您同時準備好早餐的每道菜,除吐司以外。You have everything ready for breakfast at the same time except the toast. 準備吐司是非同步作業 (烤土司) 以及同步作業 (塗上奶油和果醬) 的組合。Making the toast is the composition of an asynchronous operation (toasting the bread), and synchronous operations (adding the butter and the jam). 更新此程式碼說明一個重要概念:Updating this code illustrates an important concept:

重要

非同步作業後面接著同步工作的組合會是非同步作業。The composition of an asynchronous operation followed by synchronous work is an asynchronous operation. 換句話說,如果作業有任何部分為非同步,則整個作業是非同步。Stated another way, if any portion of an operation is asynchronous, the entire operation is asynchronous.

上述程式碼顯示您可以使用 TaskTask<TResult> 物件來保存執行中的工作。The preceding code showed you that you can use Task or Task<TResult> objects to hold running tasks. 您會 await 每個工作,再使用其結果。You await each task before using its result. 下一個步驟是建立表示其他工作組合的方法。The next step is to create methods that represent the combination of other work. 在供應早餐之前,您想要等候表示烤土司後再塗上奶油和果醬的工作。Before serving breakfast, you want to await the task that represents toasting the bread before adding butter and jam. 您可以使用下列程式碼來表示該工作: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;
}

上述方法的簽章中有 async 修飾詞。The preceding method has the async modifier in its signature. 這會通知編譯器,此方法包含 await 陳述式,其中包含非同步作業。That signals to the compiler that this method contains an await statement; it contains asynchronous operations. 此方法表示烤土司後再塗上奶油和果醬的工作。This method represents the task that toasts the bread, then adds butter and jam. 此方法會傳回 Task<TResult>,表示這三項作業的組合。This method returns a Task<TResult> that represents the composition of those three operations. 程式碼的 Main 區塊現在會變成: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!");
}

上述變更說明使用非同步程式碼的重要技術。The previous change illustrated an important technique for working with asynchronous code. 您可以透過分隔作業,將多個工作組合成傳回一個工作的新方法。You compose tasks by separating the operations into a new method that returns a task. 您可以選擇何時等候該工作。You can choose when to await that task. 您可以同時開始其他工作。You can start other tasks concurrently.

有效率地等候工作Await tasks efficiently

上述程式碼結尾的一連串 await 陳述式,可以透過 Task 類別的方法來改善。The series of await statements at the end of the preceding code can be improved by using methods of the Task class. 其中一個 API 是 WhenAll,它會傳回其引數清單中所有工作都已完成時所完成的 Task,如下列程式碼所示: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!");

另一個選項是使用 WhenAny,它會傳回其任何引數完成時所完成的 Task<Task>Another option is to use WhenAny, which returns a Task<Task> that completes when any of its arguments completes. 您可以等候傳回的工作,並知道它已完成。You can await the returned task, knowing that it has already finished. 下列程式碼範例示範如何使用 WhenAny 等候第一個工作完成,再處理其結果。The following code shows how you could use WhenAny to await the first task to finish and then process its result. 處理完成工作的結果之後,您會從傳遞至 WhenAny 的工作清單中移除該完成工作。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);
}

在所有這些變更之後,最終的程式碼版本看起來像這樣: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();
        }
    }
}

任何非同步早餐時

非同步準備的早餐最終版本大約需要15分鐘的時間,這是因為某些工作可以並存執行,而且程式碼可以同時監視多個工作,並且只在需要時才採取動作。The final version of the asynchronously prepared breakfast took roughly 15 minutes, this is because some tasks were able to run concurrently, and the code was able to monitor multiple tasks at once and only take action when it was needed.

此最終程式碼為非同步。This final code is asynchronous. 它會更精確地反映人員準備早餐的方式。It more accurately reflects how a person would cook a breakfast. 將上述程式碼與本文中的第一個程式碼範例做比較。Compare the preceding code with the first code sample in this article. 閱讀程式碼仍會清楚了解核心動作。The core actions are still clear from reading the code. 閱讀此程式碼的方式,如同閱讀本文開頭準備早餐的指示。You can read this code the same way you'd read those instructions for making a breakfast at the beginning of this article. asyncawait 之語言功能為所有遵循下列書面指示的人員提供轉譯:盡可能開始多個工作且不要防止等候工作完成。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.

後續步驟Next steps