在 .NET / .NET Core 中的异步 I/O 期间,线程池的完成端口线程如何表现?
Posted
技术标签:
【中文标题】在 .NET / .NET Core 中的异步 I/O 期间,线程池的完成端口线程如何表现?【英文标题】:How do Completion Port Threads of the Thread Pool behave during async I/O in .NET / .NET Core? 【发布时间】:2020-01-23 05:55:28 【问题描述】:.NET / .NET Core 线程池在内部使用两种不同类别的线程:工作线程和 I/O 完成端口 (IOCP) 线程。两者都只是通常的托管线程,但用于不同的目的。通过不同的 API(例如 Task.Start
或 ThreadPool.QueueUserWorkItem
),我可以在工作线程上启动 CPU 密集型异步操作(这不应该阻塞,否则线程池可能会创建额外的工作线程)。
但是执行 I/O 密集型异步操作呢? IOCP 线程在这些情况下的表现如何?具体来说,我有以下问题:
如果我启动异步 I/O 操作(例如,针对文件、管道或网络),我怀疑当前线程分派了异步请求。我也知道(通过“通过 C# 进行 CLR”一书)CLR 注册到用于执行重叠异步 I/O 的 I/O 完成端口。我怀疑这个 IOCP 是绑定到异步操作的,以便稍后可以将异步操作结果排队到线程池中。 因此,我的假设是否正确,即启动异步请求时没有触及 IOCP 线程? 我怀疑当异步 I/O 操作的结果通过 CLR 的 I/O 完成端口报告时,这就是 IOCP 线程到位的地方。结果排队到线程池,并使用一个 IOCP 线程来处理它。但是,在阅读一些论坛线程like this one on MSDN 时,我感觉IOCP 线程实际上是用于分派请求然后阻塞直到返回结果。 是这样吗?当 I/O 操作由对方系统处理时,IOCP 线程是否阻塞?async await
和 SynchronizationContext
呢? IOCP 线程是否处理异步 I/O 响应,然后例如在 UI 线程上排队继续(假设未调用 ConfigureAwait(false)
)?
Linux/MacOS X 上的 .NET Core 怎么样?没有 I/O 完成端口 - 它们是否以任何方式模拟?
【问题讨论】:
There is no thread 总是推荐在这里阅读。 这个问题太笼统了。一般来说,不,启动 I/O 请求不需要任何异步代码。 I/O 管理器必须已经处理请求 I/O 的多个进程,因此它自己负责将驱动程序请求排队。 SynchronizationContext 通常没有任何作用,但早期 .NET Framework 版本中的 XxxxAsync() 方法除外,这些方法努力使事件在预期线程上引发。 unix 有自己的风格,macOS 使用 kqueue,Linux 使用 epoll。 @Damien_The_Unbeliever 感谢您的链接,Stephen Toub 的帖子几乎回答了我所有的问题。 @HansPassant 你是对的,这个问题太宽泛了(或者没有很好地表述)。感谢您对 kqueue 和 epoll 的提示。 仅作记录,Stephen Toub 是设计大量 .NETasync
系统的天才。斯蒂芬·克利里(“另一个斯蒂芬”)就是喜欢写它的人。
【参考方案1】:
Damien 和 Hans 为我指出了 cmets 的正确方向,我想在这个答案中总结一下。
Damien 指出Stephen Cleary's awesome blog 的帖子回答了前三点:
在调用线程上调度异步 I/O 操作。不涉及 IOCP 线程。 因此,在异步 I/O 期间没有 IOCP 线程阻塞。 当结果返回给 .NET 应用程序时,会借用一个 IOCP 线程来标记任务完成。延续排队到目标SynchronizationContext
或线程池。
Hans 指出 Linux 中存在与 IOCP 类似的机制(epoll)和 MacOS (kqueue)。
【讨论】:
但是在这个 url,***.com/questions/28690815/iocp-threads-clarification,第二个答案在调用 I/O 操作时显示了Worker threads: 0, Completion port threads: 30, Total threads: 34
。 ######## 有 30 个 I/O 线程用于 I/O 操作。但是这篇文章说,I/O 线程不等待 I/O。如果为真,则应显示较少的 30 个 I/O 线程用于 I/O 操作。我无法理解。
完成端口线程将仅用于将 .NET 中的相应任务标记为已完成。如果在一个循环中启动多个FileStream
实例,那么线程池将创建更多的IOCP 线程来处理所有完成的I/O 请求包。但是,IOCP 线程在实际 I/O 操作期间不会阻塞。操作在调用线程上启动,IOCP 线程在 I/O 操作完成后将任务标记为已完成,如果有SynchronizationContext
,则将在常规后台线程或调用线程上执行继续。
谢谢,我还有一个问题。 ## 你提到异步 I/O 操作是在调用线程上调度的。 No IOCP thread is involved.
你说线程池会创建更多的IOCP线程来处理所有完成的I/O请求包。 --> 我知道在初始 I/O 操作时会创建 IOCP 线程,并等待 I/O 完成。当 IOCP 线程等待 I/O 完成时,they(IOCP threads) are blocking.
如果我有误解,请告诉我。非常感谢。
不,你错了:1) 在你当前的线程上,一个 I/O 请求包被创建并交给操作系统,它开始实际的 I/O 操作。 2)设备驱动程序由例如通知。一旦 I/O 操作完成,网络或磁盘控制器。 3) 您的 .NET 进程现在由操作系统通知 I/O 操作已完成。 .NET 运行时现在只会使用 IOCP 线程来快速将任务标记为已完成并将继续(无阻塞)排队。 4) 延续将在初始线程(如果它有 SynchronizationContext)或线程池的另一个后台线程上执行。
谢谢,我明白了。我也参考了这个 repo github.com/dschenkelman/async-io-talk.以上是关于在 .NET / .NET Core 中的异步 I/O 期间,线程池的完成端口线程如何表现?的主要内容,如果未能解决你的问题,请参考以下文章