C# 异步和等待,async/await

一个老外的博客转载的,继续涨下知识。


大多数人已经听说过 Visual Studio 11 中新的“async”和“await”功能。这是另一篇介绍性文章。

首先,重点是:异步将从根本上改变大多数代码的编写方式。

是的,我相信 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 会检查 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非异步方法的结果

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 方法,您没有任何东西可以传递给 await。

当您有异步事件处理程序时,您必须返回 void。


您还可以将 async void 用于其他“顶级”类型的操作 - 例如,控制台程序的单个“静态 async void MainAsync()”。但是,这种使用 async 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;
}

习惯这有点奇怪,但这种设计背后很好的理由

语境

在概述中,我提到当您等待内置的可等待对象时,等待对象将捕获当前的“上下文”,然后将其应用于异步方法的其余部分。这个“上下文”究竟是什么?

简单回答:

  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 一起使用来进行简单的并行计算。但是,这不能替代任务并行库——任何高级 CPU 密集型并行操作都应该使用 TPL 来完成。

指南

阅读基于任务的异步模式 (TAP) 文档它写得非常好,包括 API 设计指南和 async/await 的正确使用(包括取消和进度报告)。

应该使用许多新的等待友好的技术来代替旧的阻塞技术。如果您的新异步代码中有这些旧示例中的任何一个,那么您就做错了 (TM):

老的新的描述
task.Waitawait task等待/等待任务完成
task.Resultawait task获取完成任务的结果
Task.WaitAnyawait Task.WhenAny等待/等待一组任务中的一个完成
任务.WaitAllawait Task.WhenAll等待/等待一组任务中的每一个完成
Thread.Sleepawait Task.Delay等待/等待一段时间
Task constructorTask.Run or TaskFactory.StartNew创建基于代码的任务

下一步

我发表了一篇 MSDN 文章异步编程的最佳实践,其中进一步解释了“避免异步无效”、“一路异步”和“配置上下文”指南。

官方MSDN文档是相当不错的; 它们包括基于任务的异步模式文档的在线版本,该文档非常好,涵盖了异步方法的设计。

async 团队发布了async/await 常见问题解答,这是继续学习异步的好地方。他们有指向那里最好的博客文章和视频的指针。此外,Stephen Toub 的几乎所有博客文章都具有指导意义!

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


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864