9

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

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

示例(多合一方法,所有异步等待):

  • 将一些小更新保存到数据库
  • 使用读取一个非常小的文件上传到流ReadAsStreamAsync
  • 使用将读取流复制到文件流CopyToAsync
  • 使用刷新写入器流FlushAsync
4

2 回答 2

11

我建议您不要从时序的角度,而是从 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 限制)工作。

于 2013-10-31T11:13:42.743 回答
3

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

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

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

Charles Petzold在他的博客上写了一篇很好的文章

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

于 2013-10-31T10:34:59.223 回答