高效等待任务
可以使用 Task 类的方法来改进上述代码末尾的一系列 await 语句。其中一个 API 是 WhenAll,它返回一个 Task,该 Task 在其参数列表中的所有任务都完成后才完成,如以下代码所示:
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>,当其任何参数完成时,该 Task<Task> 就会完成。您可以等待返回的任务,因为您知道它已经完成。以下代码显示了如何使用 WhenAny 等待第一个任务完成,然后处理其结果。处理完已完成任务的结果后,您可以从传递给 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");}await finishedTask;breakfastTasks.Remove(finishedTask);
}
接近结尾处,您会看到 await finishTask; 行。await Task.WhenAny 行不等待已完成的任务。它等待 Task.WhenAny 返回的任务。Task.WhenAny 的结果是已完成(或发生故障)的任务。您应该再次等待该任务,即使您知道它已完成运行。这就是您检索其结果或确保引发其故障的异常的方式。
经过所有这些更改后,代码的最终版本如下所示:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;namespace AsyncBreakfast
{// These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.internal class Bacon { }internal class Coffee { }internal class Egg { }internal class Juice { }internal class Toast { }class Program{static async Task Main(string[] args){Coffee cup = PourCoffee();Console.WriteLine("coffee is ready");var eggsTask = FryEggsAsync(2);var baconTask = FryBaconAsync(3);var toastTask = MakeToastWithButterAndJamAsync(2);var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };while (breakfastTasks.Count > 0){Task finishedTask = await Task.WhenAny(breakfastTasks);if (finishedTask == eggsTask){Console.WriteLine("eggs are ready");}else if (finishedTask == baconTask){Console.WriteLine("bacon is ready");}else if (finishedTask == toastTask){Console.WriteLine("toast is ready");}await finishedTask;breakfastTasks.Remove(finishedTask);}Juice oj = PourOJ();Console.WriteLine("oj is ready");Console.WriteLine("Breakfast is ready!");}static async Task<Toast> MakeToastWithButterAndJamAsync(int number){var toast = await ToastBreadAsync(number);ApplyButter(toast);ApplyJam(toast);return toast;}private static Juice PourOJ(){Console.WriteLine("Pouring orange juice");return new Juice();}private static void ApplyJam(Toast toast) =>Console.WriteLine("Putting jam on the toast");private static void ApplyButter(Toast toast) =>Console.WriteLine("Putting butter on the toast");private static async Task<Toast> ToastBreadAsync(int slices){for (int slice = 0; slice < slices; slice++){Console.WriteLine("Putting a slice of bread in the toaster");}Console.WriteLine("Start toasting...");await Task.Delay(3000);Console.WriteLine("Remove toast from toaster");return new Toast();}private static async Task<Bacon> FryBaconAsync(int slices){Console.WriteLine($"putting {slices} slices of bacon in the pan");Console.WriteLine("cooking first side of bacon...");await Task.Delay(3000);for (int slice = 0; slice < slices; slice++){Console.WriteLine("flipping a slice of bacon");}Console.WriteLine("cooking the second side of bacon...");await Task.Delay(3000);Console.WriteLine("Put bacon on plate");return new Bacon();}private static async Task<Egg> FryEggsAsync(int howMany){Console.WriteLine("Warming the egg pan...");await Task.Delay(3000);Console.WriteLine($"cracking {howMany} eggs");Console.WriteLine("cooking the eggs ...");await Task.Delay(3000);Console.WriteLine("Put eggs on plate");return new Egg();}private static Coffee PourCoffee(){Console.WriteLine("Pouring coffee");return new Coffee();}}
}
异步准备早餐的最终版本大约需要 6 分钟,因为有些任务是并发运行的,并且代码同时监控多个任务,并且只在需要时采取行动。
最终代码是异步的。它更准确地反映了一个人如何做早餐。将前面的代码与本文中的第一个代码示例进行比较。从阅读代码中仍然可以清楚地看出核心操作。您可以像阅读本文开头的做早餐说明一样阅读此代码。async 和 await 的语言功能提供了每个人遵循这些书面说明所做的翻译:尽可能启动任务,不要阻止等待任务完成。