在 .NET / .NET Core 中的异步 I/O 期间,线程池的完成端口线程如何表现?

feO*_*O2x 7 .net asynchronous iocp threadpool .net-core

.NET / .NET Core 线程池在内部使用两种不同类别的线程:工作线程和 I/O 完成端口 (IOCP) 线程。两者都只是通常的托管线程,但用于不同的目的。通过不同的 API(例如Task.StartThreadPool.QueueUserWorkItem),我可以在工作线程上启动受 CPU 限制的异步操作(不应阻塞,否则线程池可能会创建额外的工作线程)。

但是如何执行受 I/O 限制的异步操作呢?在这些情况下,IOCP 线程的行为究竟如何?具体来说,我有以下问题:

  • 如果我开始一个异步 I/O 操作(例如文件、管道或网络),我怀疑当前线程会分派异步请求。我也知道(通过“CLR via C#”一书)CLR 注册到一个 I/O 完成端口,该端口用于执行重叠异步 I/O。我怀疑这个 IOCP 是绑定到异步操作上的,以便稍后可以将异步操作结果排队到线程池中。因此,我的假设是否正确,即在启动异步请求时没有触及 IOCP 线程?
  • 我怀疑当异步 I/O 操作的结果通过 CLR 的 I/O 完成端口上报时,这就是 IOCP 线程出现的地方。结果被排队到线程池中,并使用一个 IOCP 线程来处理它。但是,当阅读MSDN 上的一些论坛主题时,我觉得 IOCP 线程实际上是用来分派请求然后阻塞直到结果回来。是这种情况吗?当 I/O 操作由对方系统处理时,IOCP 线程是否阻塞?
  • 怎么样async awaitSynchronizationContextIOCP 线程是否处理异步 I/O 响应,然后例如将 UI 线程上的延续排队(假设ConfigureAwait(false)未调用)?
  • Linux / MacOS X 上的 .NET Core 怎么样?没有 I/O 完成端口 - 它们是否以任何方式模拟?

feO*_*O2x 6

Damien 和 Hans 在评论中为我指出了正确的方向,我想在这个答案中总结一下。

Damien 指出了Stephen Cleary 的精彩博客文章,它回答了前三点:

  • 异步 I/O 操作在调用线程上分派。不涉及 IOCP 线程。
  • 因此,在异步 I/O 期间没有 IOCP 线程阻塞。
  • 当结果返回给 .NET 应用程序时,将借用 IOCP 线程来标记任务完成。延续排队到目标SynchronizationContext或线程池。

Hans指出Linux(epoll)和MacOS(kqueue)中也有类似IOCP的机制。

  • 完成端口线程将仅短暂用于将 .NET 中的相应任务标记为已完成。如果您在循环中启动多个“FileStream”实例,则线程池将创建更多 IOCP 线程来处理所有完成的 I/O 请求数据包。但是,IOCP 线程在实际 I/O 操作期间不会阻塞。该操作在调用线程上启动,一旦 I/O 操作完成,IOCP 线程将任务标记为已完成,并且如果存在“SynchronizationContext”,则将在常规后台线程或调用线程上执行延续。 (3认同)
  • 谢谢你,我还有一个问题。## 您提到异步 I/O 操作是在调用线程上分派的。`不涉及IOCP线程。`并且您说线程池将创建更多IOCP线程来处理所有完成的I/O请求数据包。--> 我知道 IOCP 线程将在初始 I/O 操作时创建,并等待 I/O 完成。当 IOCP 线程等待 I/O 完成时,“它们(IOCP 线程)正在阻塞。”如果我有误解,请告诉我。多谢。 (3认同)
  • 不,你错了:1)在当前线程上,创建了一个 I/O 请求数据包并将其传递给启动实际 I/O 操作的操作系统。2) 一旦I/O 操作完成,设备驱动程序就会收到网络或磁盘控制器的通知。3) 现在,操作系统通知您的.NET 进程 I/O 操作已完成。.NET 运行时现在只会使用 IOCP 线程快速将任务标记为已完成并将继续任务排入队列(无阻塞)。4) 延续将在初始线程(如果它有 SynchronizationContext)或线程池的另一个后台线程上执行。 (2认同)
  • @RobL 抱歉花了这么长时间。刚刚注意到你的问题。线程池线程在不使用时被创建并置于睡眠状态。这意味着它们驻留在内存中,但不会在其上执行代码。仅当您针对线程池运行任务时,才会选择一个线程并将其状态从睡眠更改为等待执行。操作系统可以在上下文切换期间选择该线程并将其分配到 CPU 核心上执行。只有这样任务的代码才会被执行。一旦工作完成,线程不会死亡,而是进入睡眠状态或分配下一个排队的任务。 (2认同)