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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Charles Petzold 在他的博客上写了一篇关于它的好文章 。

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