上次,我们介绍了《如何保证执行异步方法时不会遗漏 await 关键字》。
但是,对于async/await 的误用不仅于此。
有些方法不需要使用async/await。添加异步修饰符是有代价的:编译器将在每个异步方法中生成一些代码。
下列代码开启了一个外部 Task,并不需要等待完成:
- //修改前
- public static async Task Demo()
- {
- await Task.Factory.StartNew(() => Console.WriteLine("My IO"));
- }
-
- //修改后
- public static Task Demo()
- {
- return Task.Factory.StartNew(() => Console.WriteLine("My IO"));
- }
在异步方法中使用一些可能长时间运行或阻塞的操作,即使有这些方法的相应异步版本。
下列代码使用了ReadToEnd
,而不是对应的异步版本:
- //修改前
- public static async Task Demo()
- {
- StreamReader reader = GetReader();
-
- var str = reader.ReadToEnd();
- }
-
- //修改后
- public static async Task Demo()
- {
- StreamReader reader = GetReader();
-
- var str = await reader.ReadToEndAsync();
- }
异步 void 方法中的异常无法在调用方法中捕获。
下列代码运行时并不会抛出异常:
- //修改前
- public static async void Demo()
- {
- throw new Exception();
- await Task.Delay(300);
- await Task.Delay(300);
- }
-
- //修改后
- public static async Task Demo()
- {
- throw new Exception();
- await Task.Delay(300);
- await Task.Delay(300);
- }
在 using 块内,执行了异步方法但未 await,它可能导致潜在的异常或错误的结果。
下列代码中的CopyToAsync
如果持续时间很长,将抛出 ObjectDisposedException:
- //修改前
- using (var stream = new FileStream("newfile.txt", FileMode.Open))
- {
- newStream.CopyToAsync(stream);
- }
-
- //修改后
- using (var stream = new FileStream("newfile.txt", FileMode.Open))
- {
- await newStream.CopyToAsync(stream);
- }
这通常发生在将 async/await 关键字与 Task.Factory.StartNew 混用的情况,没有办法等待并获得子任务的结果。
下列代码中的意图是进行一秒的延迟,但实际不会有任何延迟:
- //修改前
- public static async Task Demo()
- {
- Console.WriteLine("Hello");
- await Task.Factory.StartNew(() => Task.Delay(1000));
- Console.WriteLine("My IO");
- }
-
- //修改后
- public static async Task Demo()
- {
- Console.WriteLine("Hello");
- await Task.Run(() => Task.Delay(1000));
- Console.WriteLine("My IO");
- }
但是要完全靠人去识别这些错误有点难度,这里介绍一个查找和纠正 async/await 的常见误用的工具 —— AsyncFixer。
引用 Nuget 包 AsyncFixer 后, 在 VS 中就会将这些误用视为警告,你也可以像上次的文章一样将严重性调为“错误":
同时,分析器也提供了如何纠正的建议:
今天,我们介绍了 async/await 的常见误用,以及如何纠正。