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 BlogJon 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方法的其余部分。“上下文”到底是什么?

简单来说:

  1. 如果您使用的是UI线程,则它是一个UI上下文。
  2. 如果您要响应一个ASP.NET请求,那么它就是一个ASP.NET请求上下文。
  3. 否则,通常是线程池上下文。

复杂来说:

  1. 如果SynchronizationContext.Current不为null,则为当前SynchronizationContext。(UI和ASP.NET请求上下文是SynchronizationContext上下文)。
  2. 否则,它是当前的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):

OldNewDescription
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撰写的几乎所有博客文章都具有启发性

当然,另一个资源是我自己的博客。

posted @ 2020-04-12 11:29  Nine4酷  阅读(229)  评论(0)    收藏  举报