异步/等待模式是否会对次要工作单元造成性能损失?

时间:2013-10-31 06:12:30

标签: c# .net asynchronous async-await

我正在寻找关于异步/等待有意义的“工作负载阈值”(例如释放IO完成端口并避免线程饥饿)与工作单元执行过于简单/便宜之间的一些指导,所以同步执行是一个更好的选择。

换句话说,当与相对快速/资源廉价的工作单元结合使用时,async / await的使用会导致性能下降,并且只是同步执行工作会是首选方法吗?

示例(所有在一个方法中,都与await异步):

  • 保存对DB的一些小更新
  • 使用ReadAsStreamAsync
  • 读取非常小的文件上传到流
  • 使用CopyToAsync
  • 将读取流复制到文件流
  • 使用FlushAsync
  • 刷新编写器流

2 个答案:

答案 0 :(得分:10)

我建议你不是从时间角度来看,而是从I / O-vs-CPU的角度来看待它。

CPU绑定方法自然是同步的; I / O绑定方法自然是异步的。

我假设你的环境是服务器端的,基于你的例子:

  • 保存对DB 的一些小更新。自然异步:网络(或至少是进程外)通信,争用的可能性,磁盘I / O.
  • 使用ReadAsStreamAsync 读取非常小的文件上传到流。自然异步:网络通信。
  • 使用CopyToAsync 将读取流复制到文件流。自然异步:争用的可能性,磁盘I / O.
  • 使用FlushAsync刷新编写器流。自然异步:争用的可能性,磁盘I / O.

所有这些都是自然异步操作,因此它们都应该异步实现。 CPU比内存快得多,这比网络或磁盘I / O快得多。因此,如果同步实现自然异步方法,阻塞线程。这不是世界末日,但线程池必须补偿它是否被阻塞太久,而唯一一次“保存”是线程切换时间,这将是数量级更短比任何网络或磁盘I / O都可能

要考虑的另一件事是不可预测的延迟,这通常是由于资源争用而发生的。如果另一个进程同时写入db,该怎么办?如果文件上传时出现路由打嗝,需要重新传输数据包怎么办?如果在您尝试写入输出文件时磁盘进行碎片整理会怎样?异步操作倾向于像这样不可预测,并且async代码确保您不会比预期更长时间地阻塞线程。

总之:对同步(CPU绑定)工作使用同步代码,对异步(I / O绑定)工作使用异步代码。

答案 1 :(得分:3)

首先,您应该知道仅仅因为使用await关键字异步调用方法,这并不意味着该方法无法同步运行。或者确切地说:此类方法通常会返回TaskTask<T>IAsyncOperation<TResult>(在Windows运行时中),并且该方法返回时很可能完成该任务。在这种情况下,开销非常小,因为执行的线程将继续运行。

至于阈值本身,这取决于您想要做什么以及您在哪个环境中运行。这是UI应用程序还是服务器应用程序?您想异步运行以释放UI,还是更好地(即更具可伸缩性)使用服务器线程?

对于Windows运行时API,Microsoft使用了50毫秒的阈值,这意味着任何可能需要超过50毫秒才能执行的方法仅以异步形式提供。这背后的逻辑非常简单:这样做可以使UI线程执行长程序,并且永远不会被阻塞超过50毫秒。换句话说,UI线程可以运行其他有用的代码,例如,渲染帧,每秒20次或更多。

Charles Petzold写道a good article about it on his blog

对于服务器场景,只要通过释放线程完成的工作不仅仅是释放线程所需的工作,就可以异步运行。根据我的经验,几乎所有IO都是如此。当然有一些看起来像IO的例外,但它实际上是在读取或写入内存缓冲区,但在这些情况下,该方法返回的任务将同步完成。