Async and Await 异步并等待
本文是查问题过程中找到的比较好解答,为了记录和便于他人阅读,转载过来(工具翻译过来,翻译的还不错,修改了部分翻译错误,建议先阅读原文再读译文,有助于提升英文资料能力):
原文地址:https://blog.stephencleary.com/2012/02/async-and-await.html
大多数人已经听说过Visual Studio 11中新的“异步”和“等待”功能。这是另一篇介绍性文章。
首先,要点:异步将从根本上改变大多数代码的编写方式。
是的,我相信async/await将比LINQ产生更大的影响。从现在开始的短短几年内,了解异步将是一项基本必要。
关键字介绍
让我们继续深入。我将使用稍后将阐述的一些概念-坚持第一部分。
异步方法如下所示:
public async Task DoSomethingAsync()
{
// In the Real World, we would actually do something...
// For this example, we're just going to (asynchronously) wait 100ms.
await Task.Delay(100);
}
“ async”关键字在该方法中启用“ await”关键字,并更改如何处理方法结果。这就是async关键字所做的全部!它不会在线程池线程上运行此方法,也不会执行任何其他魔术。async关键字仅启用await关键字(并管理方法结果)。
异步方法的开始与其他任何方法一样执行。也就是说,它会同步运行,直到达到“ await”(或引发异常)为止。
“ await”关键字是事物可以异步进行的地方。Await就像一元运算符:它接受一个参数,一个awaitable(“ awaitable”是一个异步操作)。Await检查该等待项是否已经完成;如果等待已完成,则该方法将继续运行(同步,就像常规方法一样)。
如果“ await”发现awaitable尚未完成,则它将异步执行。它告诉等待的对象在完成时运行该方法的其余部分,然后从异步方法中返回。
稍后,当awaitable完成时,它将执行async方法的其余部分。如果您正在等待内置的等待(例如任务),那么异步方法的其余部分将在返回“等待”之前捕获的“上下文”上执行。
我喜欢将“等待”视为“异步等待”。也就是说,async 方法会暂停直到awaitable完成(因此它等待),但是实际线程不会被阻塞(因此它是异步的)。
Await
如前所述,“ await”采用一个参数-“ awaitable”-这是一个异步操作。.NET框架中已经有两种常见的等待类型:Task <T>和Task。
还有其他等待类型:特殊方法(例如“ Task.Yield”)返回不是Task的等待类型,而WinRT运行时(Windows 8中即将推出)具有非托管的等待类型。您也可以创建自己的awaitable(通常出于性能原因),或使用扩展方法使aawaitable类型变为awaitable。
我要说的就是关于制作自己的等待者。在使用async / await的整个过程中,我只需要编写几个awaitables。如果您想了解更多有关编写自己的等待者的信息,请参阅Parallel Team Blog或Jon Skeet的Blog。
关于awaitables的重要一点是:它是可等待的类型,而不是返回该类型的方法。换句话说,您可以等待返回Task的异步方法的结果… 因为该方法返回Task,而不是因为它是async。因此,您也可以等待返回Task 的非异步方法的结果:
public async Task NewStuffAsync()
{
// Use await and have fun with the new stuff.
await ...
}
public Task MyOldTaskParallelLibraryCode()
{
// Note that this is not an async method, so we can't use await in here.
...
}
public async Task ComposeAsync()
{
// We can await Tasks, regardless of where they come from.
await NewStuffAsync();
await MyOldTaskParallelLibraryCode();
}
提示:如果您有一个非常简单的异步方法,则可以在不使用await关键字的情况下编写它(例如,从另一个方法返回任务)。但是,请注意,选择和时asyncawait存在陷阱。
返回类型
异步方法可以返回Task <T>,Task或void。在几乎所有情况下,您都想着返回Task <T>或Task,并且仅在必要时才返回void。
为什么返回Task <T>或Task?因为它们是可以等待的,而void不是。因此,如果您有一个异步方法返回Task <T>或Task,则可以将结果传递给await。使用void方法,您无需等待任何内容。
当您具有异步事件处理程序时,必须返回void。
您还可以对其他“顶级”操作使用异步void-例如,对控制台程序使用单个“静态异步void MainAsync()”。但是,异步void的使用有其自身的问题。请参阅异步控制台程序。异步void方法的主要用例是事件处理程序。
返回值
返回Task或void的异步方法没有返回值。返回Task <T>的异步方法必须返回T类型的值:
public async Task<int> CalculateAnswer()
{
await Task.Delay(100); // (Probably should be longer...)
// Return a type of "int", not "Task<int>"
return 42;
}
习惯有点奇怪,但是这种设计背后有充分的理由。
语境
在概述中,我提到当您等待内置的await时,await会捕获当前的“上下文”,然后将其应用于async方法的其余部分。“上下文”到底是什么?
简单来说:
- 如果您使用的是UI线程,则它是一个UI上下文。
- 如果您要响应一个ASP.NET请求,那么它就是一个ASP.NET请求上下文。
- 否则,通常是线程池上下文。
复杂来说:
- 如果SynchronizationContext.Current不为null,则为当前SynchronizationContext。(UI和ASP.NET请求上下文是SynchronizationContext上下文)。
- 否则,它是当前的TaskScheduler(TaskScheduler.Default是线程池上下文)。
这在现实世界中意味着什么?一方面,捕获(和还原)UI / ASP.NET上下文是透明完成的:
// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
// Since we asynchronously wait, the UI thread is not blocked by the file download.
await DownloadFileAsync(fileNameTextBox.Text);
// Since we resume on the UI context, we can directly access UI elements.
resultTextBox.Text = "File downloaded!";
}
// ASP.NET example
protected async void MyButton_Click(object sender, EventArgs e)
{
// Since we asynchronously wait, the ASP.NET thread is not blocked by the file download.
// This allows the thread to handle other requests while we're waiting.
await DownloadFileAsync(...);
// Since we resume on the ASP.NET context, we can access the current request.
// We may actually be on another *thread*, but we have the same ASP.NET request context.
Response.Write("File downloaded!");
}
这对事件处理程序非常有用,但是事实证明,这并不是大多数其他代码(实际上,您将要编写的大多数异步代码)所需要的。
避开上下文
大多数时候,您不需要同步回“主”上下文。大多数异步方法在设计时都会考虑到组合:它们等待其他操作,每个异步方法本身都代表一个异步操作(可以由其他操作组成)。在这种情况下,您想通过调用ConfigureAwait并传递false 来告诉等待者不要捕获当前上下文,例如:
private async Task DownloadFileAsync(string fileName)
{
// Use HttpClient or whatever to download the file contents.
var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false);
// Note that because of the ConfigureAwait(false), we are not on the original context here.
// Instead, we're running on the thread pool.
// Write the file contents out to a disk file.
await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);
// The second call to ConfigureAwait(false) is not *required*, but it is Good Practice.
}
// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
// Since we asynchronously wait, the UI thread is not blocked by the file download.
await DownloadFileAsync(fileNameTextBox.Text);
// Since we resume on the UI context, we can directly access UI elements.
resultTextBox.Text = "File downloaded!";
}
在此示例中要注意的重要一点是,异步方法调用的每个“级别”都有其自己的上下文。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一起使用,以进行简单的并行计算。但是,这不能替代Task Parallel Library-任何高级CPU密集型并行操作都应使用TPL完成。
指导方针
阅读基于任务的异步模式(TAP)文档。它写得很好,并包含有关API设计和正确使用async / await(包括取消和进度报告)的指南。
有许多新的等待友好技术应被使用,以代替旧的阻塞技术。如果新的异步代码中有这些旧示例中的任何一个,则说明您正在做错(TM):
| Old | New | Description |
|---|---|---|
| task.Wait | await task | Wait/await for a task to complete |
| task.Result | await task | Get the result of a completed task |
| Task.WaitAny | await Task.WhenAny | Wait/await for one of a collection of tasks to complete |
| Task.WaitAll | await Task.WhenAll | Wait/await for every one of a collection of tasks to complete |
| Thread.Sleep | await Task.Delay | Wait/await for a period of time |
| Task constructor | Task.Run or TaskFactory.StartNew | Create a code-based task |
下一步
我已经发表了MSDN文章“异步编程最佳实践”,其中进一步解释了“避免异步作废”,“一路异步”和“配置上下文”指南。
该官方MSDN文档是相当不错的; 它们包括基于任务的异步模式文档的在线版本,该文档非常出色,涵盖了异步方法的设计。
异步团队已发布了异步/等待常见问题解答,这是继续学习异步的好地方。他们可以找到最好的博客文章和视频。此外,Stephen Toub撰写的几乎所有博客文章都具有启发性!
当然,另一个资源是我自己的博客。
浙公网安备 33010602011771号