添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
胆小的马克杯  ·  Quick start - ...·  2 周前    · 
善良的花卷  ·  Player_Flutter/Flutter ...·  6 天前    · 
谦和的蘑菇  ·  a steep learning ...·  3 月前    · 
千杯不醉的人字拖  ·  Unable to load the ...·  1 年前    · 

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 关键字的情况下编写它(例如,从另一个方法返回一个任务)。 但是,请注意, 在省略 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 类型的值:

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.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 的 几乎所有博客文章都 具有指导意义!

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


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