async和await的使用总结 ~ 竟然一直用错了c#中的async和await的使用。。
对于c#中的async和await的使用,没想到我一直竟然都有一个错误。。
。。还是总结太少,这里记录下。
这里以做早餐为例
流程如下:
- 倒一杯咖啡。
- 加热平底锅,然后煎两个鸡蛋。
- 煎三片培根。
- 烤两片面包。
- 在烤面包上加黄油和果酱。
- 倒一杯橙汁。
当使用同步方式实现时,代码是这样的:
using System; using System.Diagnostics; using System.Threading.Tasks; namespace AsyncBreakfast { class Program { static void Main(string[] args) { var sw = new Stopwatch(); sw.Start(); 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!"); Console.WriteLine($"totol time:{sw.ElapsedMilliseconds/1000}"); Console.ReadKey(); } 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(); } } class Coffee { } class Egg { } class Bacon { } class Toast { } class Juice { } }
运行效果如下:
或表示为这样
同步准备的早餐大约花费了 30 分钟,因为总耗时是每个任务耗时的总和。这里的total time只是用来表示记录下程序运行的时间。
而我以前写的异步代码是这样的:
using System; using System.Diagnostics; using System.Threading.Tasks; namespace AsyncBreakfast { class Program { static async void Main(string[] args) { var sw = new Stopwatch(); sw.Start(); 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!"); Console.WriteLine($"totol time:{sw.ElapsedMilliseconds/1000}"); Console.ReadKey(); } 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(); } } class Coffee { } class Egg { } class Bacon { } class Toast { } class Juice { } }
效果如下:
可以看出,这样编写的异步和最初同步版本的总共的耗时大致相同。
这是因为这段代码还没有利用异步编程的某些关键功能。
即上面的异步代码的使用在这里是不准确的。
可以看出,这段代码里面的打印输出与同步是一样的。
这是因为:在煎鸡蛋或培根时,此代码虽然不会阻塞,但是此代码也不会启动任何其他任务。
就造成了异步煎鸡蛋的操作完成后,才会开始培根制作。
但是,对于这里而言,我不希望每个任务都按顺序依次执行。
最好是首先启动每个组件任务,然后再等待之前任务的完成。
例如:首先启动鸡蛋和培根。
同时启动任务
在很多方案中,你可能都希望立即启动若干独立的任务。然后,在每个任务完成时,你可以继续
进行已经准备的其他工作。
就像这里同时启动煎鸡蛋,培根和烤面包。
我们这里对早餐代码做些更改。
正确的做法
第一步是存储任务以便在这些任务启动时进行操作,而不是等待:
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语句移动到此方法的末尾:
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分钟,这是因为一些任务可以并发进行。
而对于直接 Egg eggs = await FryEggsAsync(2); 的方式,适用于你只需要等待这一个异步操作结果,不需要进行其他操作的时候。
与任务组合
吐司操作由异步操作(烤面包)和同步操作(添加黄油和果酱)组成。
这里涉及到一个重要概念:
异步操作后跟同步操作的这种组合也是一个异步操作。
也就是说,如果操作的任何部分是异步的,整个操作就是异步的。
代码如下:
static async Task<Toast> MakeToastWithButterAndJamAsync(int number) { var toast = await ToastBreadAsync(number); ApplyButter(toast); ApplyJam(toast); return toast; }
所有,主要代码块现在变为:
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!"); }
高效的等待任务
可以通过使用Task类的方法改进上述代码末尾一系列await语句。
WhenAll 是其中的一个api , 它将返回一个其参数列表中的所有任务都已完成时猜完成的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>。
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); }
处理已完成任务的结果之后,可以从传递给 WhenAny
的任务列表中删除此已完成的任务。
进行这些更改后,代码的最终版本将如下所示:
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分钟,因为一些任务能同时运行,
并且该代码能够同时监视多个任务,只在需要时才执行操作。
总结:
async 和 await的功能最好能做到:
尽可能启动任务,不要在等待任务完成时造成阻塞。
即可以先把任务存储到task,然后在后面需要用的时候,调用await task()方法。
参考网址:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/