一个老外的博客转载的,继续涨下知识。
大多数人已经听说过 Visual Studio 11 中新的“async”和“await”功能。这是另一篇介绍性文章。
首先,重点是:异步将从根本上改变大多数代码的编写方式。
是的,我相信 async/await 会比 LINQ 产生更大的影响。 理解异步将在短短几年内成为基本必需品。
关键字介绍
让我们直接开始吧。我将使用一些稍后将阐述的概念——请继续阅读第一部分。
异步方法看起来像这样:
“async”关键字在该方法中启用“await”关键字并更改方法结果的处理方式。 这就是 async 关键字所做的一切! 它不会在线程池线程上运行此方法,也不会执行任何其他类型的魔术。 async 关键字 仅 启用 await 关键字(并管理方法结果)。
异步方法的开头就像任何其他方法一样执行。 也就是说,它会同步运行,直到遇到“await”(或抛出异常)。
“await”关键字是事情可以异步的地方。 Await 就像一个一元运算符:它接受一个参数,一个 awaitable (一个“awaitable”是一个异步操作)。 Await 会检查 awaitable 是否已经完成; 如果 awaitable 已经完成,则该方法将继续运行(同步,就像常规方法一样)。
如果“await”看到awaitable 尚未完成,则它会异步执行。 它告诉 awaitable 在完成时运行该方法的其余部分,然后 从异步方法 返回 。
稍后,当等待完成时,它将执行异步方法的其余部分。 如果您正在等待内置的可等待对象(例如任务),则异步方法的其余部分将在“await”返回之前捕获的“上下文”上执行。
我喜欢将“await”视为“异步等待”。 也就是说, async 方法会 暂停,直到 awaitable 完成(因此它 等待 ),但实际 线程 并未被阻塞(因此它是 异步的 )。
待办事项
正如我提到的,“await”接受一个参数——一个“awaitable”——这是一个异步操作。 .NET 框架中已经有两种常见的可等待类型:Task<T> 和 Task。
还有其他可等待类型:诸如“Task.Yield”之类的特殊方法返回不是任务的可等待对象,并且 WinRT 运行时(在 Windows 8 中推出)具有非托管的可等待类型。 您还可以创建自己的可等待(通常是出于性能原因),或使用扩展方法使不可等待类型成为可等待类型。
这就是我要说的关于制作自己的可等待对象的全部内容。 在使用 async/await 的整个过程中,我只需要编写几个可等待对象。 如果您想了解更多关于编写自己的可等待对象的信息,请参阅 Parallel Team 博客 或 Jon Skeet 的博客 。
关于 awaitables 的一个重要点是:它是 可等待 的 类型 ,而不是返回类型的方法。 换句话说,您可以等待返回 Task 的异步方法的结果…… 因为该方法返回 Task,而不是因为它是 async 。 因此,您还可以等待 返回 Task 的 非异步 方法 的结果 :
提示:如果您有一个非常简单的异步方法,您可以在不使用 await 关键字的情况下编写它(例如,从另一个方法返回一个任务)。
但是,请注意,
在省略
async
和
时
await
存在
陷阱
。
返回类型
异步方法可以返回 Task<T>、Task 或 void。 在几乎所有情况下,您都希望返回 Task<T> 或 Task,并且仅在必要时返回 void。
为什么要返回 Task<T> 或 Task? 因为它们是可等待的,而 void 不是。 因此,如果您有一个异步方法返回 Task<T> 或 Task,那么您可以将结果传递给 await。 使用 void 方法,您没有任何东西可以传递给 await。
当您有异步事件处理程序时,您必须返回 void。
您还可以将 async void 用于其他“顶级”类型的操作 - 例如,控制台程序的单个“静态 async void MainAsync()”。 但是,这种使用 async void 有其自身的问题; 请参阅 异步控制台程序 。 异步 void 方法的主要用例是事件处理程序。
返回值
返回 Task 或 void 的异步方法没有返回值。 返回 Task<T> 的异步方法必须返回 T 类型的值:
习惯这有点奇怪,但 这种设计背后 有 很好的理由 。
语境
在概述中,我提到当您等待内置的可等待对象时,等待对象将捕获当前的“上下文”,然后将其应用于异步方法的其余部分。 这个“上下文”究竟是什么?
简单回答:
-
如果您在 UI 线程上,那么它就是 UI 上下文。
-
如果您正在响应 ASP.NET 请求,则它是一个 ASP.NET 请求上下文。
-
否则,它通常是一个线程池上下文。
复杂的答案:
-
如果 SynchronizationContext.Current 不为 null,则它是当前 SynchronizationContext。 (UI 和 ASP.NET 请求上下文是 SynchronizationContext 上下文)。
-
否则,它是当前的 TaskScheduler(TaskScheduler.Default 是线程池上下文)。
这在现实世界中意味着什么? 一方面,捕获(和恢复)UI/ASP.NET 上下文是透明的:
这对于事件处理程序来说非常有用,但结果证明它不是您想要的大多数其他代码(实际上,您将编写的大多数异步代码)。
避免上下文
大多数情况下,您 不需要 同步回“主”上下文。 大多数异步方法在设计时都会考虑到组合:它们等待其他操作,每个方法都代表一个异步操作本身(可以由其他操作组合)。 在这种情况下,您希望 通过调用 ConfigureAwait 并传递 false 来告诉等待者 不要 捕获当前上下文 ,例如:
这个例子需要注意的重要一点是异步方法调用的每个“级别”都有自己的上下文。 DownloadFileButton_Click 在 UI 上下文中启动,并调用 DownloadFileAsync。 DownloadFileAsync 也在 UI 上下文中启动,但随后通过调用 ConfigureAwait(false) 退出其上下文。 DownloadFileAsync 的其余部分在线程池上下文中运行。 但是,当 DownloadFileAsync 完成并且 DownloadFileButton_Click 恢复时,它 会 在 UI 上下文 中 恢复。
一个好的经验法则是使用 ConfigureAwait(false) 除非您知道您 确实 需要上下文。
异步组合
到目前为止,我们只考虑了串行组合:一个异步方法一次等待一个操作。 也可以启动多个操作并等待其中一个(或全部)完成。 您可以通过启动操作来完成此操作,但不要等到稍后执行:
public async Task DoOperationsConcurrentlyAsync() Task[] tasks = new Task[3]; tasks[0] = DoOperation0Async(); tasks[1] = DoOperation1Async(); tasks[2] = DoOperation2Async(); // At this point, all three tasks are running at the same time. // Now, we await them all. await Task.WhenAll(tasks); public async Task<int> GetFirstToRespondAsync() // Call two web services; take the first response. Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() }; // Await for the first one to respond. Task<int> firstTask = await Task.WhenAny(tasks); // Return the result. return await firstTask; }
通过使用并发组合(Task.WhenAll 或 Task.WhenAny),您可以执行简单的并发操作。 您还可以将这些方法与 Task.Run 一起使用来进行简单的并行计算。 但是,这不能替代任务并行库——任何高级 CPU 密集型并行操作都应该使用 TPL 来完成。
指南
阅读 基于任务的异步模式 (TAP) 文档 。 它写得非常好,包括 API 设计指南和 async/await 的正确使用(包括取消和进度报告)。
应该使用许多新的等待友好的技术来代替旧的阻塞技术。 如果您的新异步代码中有这些旧示例中的任何一个,那么您就做错了 (TM):
老的 | 新的 | 描述 |
---|---|---|
task.Wait | await task | 等待/等待任务完成 |
task.Result | await task | 获取完成任务的结果 |
Task.WaitAny | await Task.WhenAny | 等待/等待一组任务中的一个完成 |
任务.WaitAll | await Task.WhenAll | 等待/等待一组任务中的每一个完成 |
Thread.Sleep | await Task.Delay | 等待/等待一段时间 |
Task constructor | Task.Run or TaskFactory.StartNew | 创建基于代码的任务 |
下一步
我发表了一篇 MSDN 文章 异步编程的最佳实践 ,其中进一步解释了“避免异步无效”、“一路异步”和“配置上下文”指南。
该 官方MSDN文档 是相当不错的; 它们包括 基于任务的异步模式文档 的在线版本,该 文档 非常好,涵盖了异步方法的设计。
async 团队发布了 async/await 常见问题解答 ,这是继续学习异步的好地方。 他们有指向那里最好的博客文章和视频的指针。 此外, Stephen Toub 的 几乎所有博客文章都 具有指导意义!
当然,另一个资源是我自己的博客。