IOCP是在I/O发生时或之后运行的线程吗?

use*_*000 7 .net c# multithreading asynchronous async-await

我想了解I/O完成端口,具体如何与使用async- await为I/O.

臭名昭着的文章没有线程谈论在I/O完成后短暂借用IOCP.因为本文的重点是要表明当花哨的硬件级I/O内容在飞行中时,没有一个线程被循环消耗

I/O完成了吗?没有.I/O已经完成了吗?没有.I/O已经完成了吗?不...

但后来我正在看这篇文章说的那个

"组件负责检查排队元素的完成端口"

并给出一个例子

public class IOCompletionWorker
{ 
    public unsafe void Start(IntPtr completionPort)
    {
        while (true)
        {
            uint bytesRead;
            uint completionKey;
            NativeOverlapped* nativeOverlapped;

            var result = Interop.GetQueuedCompletionStatus(
                completionPort, 
                out bytesRead,
                out completionKey,
                &nativeOverlapped, 
                uint.MaxValue);

            var overlapped = Overlapped.Unpack(nativeOverlapped);

            if (result)
            {
                var asyncResult = ((FileReadAsyncResult)overlapped.AsyncResult);
                asyncResult.ReadCallback(bytesRead, asyncResult.Buffer);
            }
            else
            {
                ThreadLogger.Log(Interop.GetLastError().ToString());
            }

            Overlapped.Free(nativeOverlapped);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
var completionPortThread = new Thread(() => new IOCompletionWorker().Start(completionPortHandle))
{
    IsBackground = true
};
completionPortThread.Start();
Run Code Online (Sandbox Code Playgroud)

对我来说,看起来有一些民意调查正在进行中.

我想我的问题归结为

  • 是否真的说.NET应用程序有两种类型的线程池 - (1)"工作线程"和(2)"I/O线程"?
  • 如果是真的,是否有一个固定的数字,在配置中指定,如M工作线程和NI/O线程?通常M与N的比例是多少?
  • 什么时候使用I/O线程?

fbr*_*eau 7

两篇文章都以自己的方式是正确的.

IOCP不是线程.它们可以被视为某种队列,其中内核(或者常规用户模式代码,通过PostQueuedCompletionStatus)可以发布完成项.没有与IOCP本身相关的固有线程模型或线程,它们只是多个生产者 - 消费者队列.

我们以网络套接字为例,但对于任何类型的异步工作都是如此:

  • 您在绑定到IOCP的重叠模式套接字上调用WSARecv,由网络驱动程序执行设置实际接收数据请求所需的任何操作.没有线程主动等待您的数据到达.
  • 数据到货.操作系统被硬件唤醒.操作系统将在内核中为网络驱动程序提供一些CPU时间来处理传入的事件.网络驱动程序处理中断,然后因为您的套接字绑定到IOCP,将完成项发布到IOCP队列.请求已完成.

在任何此操作中都没有涉及您的进程的实际用户模式线程(除了初始异步调用之外).如果你想对你的数据到达这一事实采取行动(我假设你在从套接字读取时就这样做了!),那么你必须从IOCP中将已完成的项目出列.

IOCP的关键是您可以将数千个IO句柄(套接字,文件......)绑定到单个IOCP.然后,您可以使用单个线程并行驱动数千个异步进程.

是的,执行GetQueuedCompletionStatus的一个线程被封锁,而IOCP上没有完成待处理,所以这可能是你的混乱所在.但IOCP的关键在于您阻止了一个线程,而您可以在任何给定时间处理数十万个网络操作,所有这些都由您的一个线程提供服务.你永远不会在IO句柄/ IOCP /服务线程之间进行1对1对1的映射,因为那样你就会失去异步的任何好处,你也可以只使用同步IO.

IOCP的主要目的是在Windows下实现令人印象深刻的异步操作并行性.

我希望这能澄清这种混乱.

至于具体问题

  1. 是的,.Net框架有两个池.一个是纯粹用于用户模式的通用工作,即"Worker"线程池.另一个是"IO"线程池.第二个是在编写高级C#代码时可以隐藏所有IOCP管理,这样你的异步套接字就像魔术一样.
  2. 这是可以随时更改的所有实现细节,但答案是两个池都是独立的.如果工作线程池上发生了大量工作带宽,并且框架决定通过添加新线程来增加总体吞吐量,则它将单独向工作池添加线程,而不是触及IO池.对于IO池也是如此,如果您的回调中存在阻塞IO线程的行为错误的代码,它将生成新的IO池而不会触及工作池.您可以使用ThreadPool.SetMinThreads/SetMaxThreads自定义数字,但这通常表示您的进程误用了线程池.
  3. 当从线程池的内部IOCP中出队的项目时,将使用IO线程.在典型的代码中,这将是在某个IO句柄上完成异步操作的时候.您也可以通过UnsafeQueueNativeOverlapped自行排队项目,但这种情况不常见.

纯托管异步操作(例如,使用Task.Delay执行async-await)不涉及任何IO句柄,因此它们最终不会被某个驱动程序发布到IOCP,因此这些操作将属于"工人"类别.

作为旁注,您可以通过其callstack告诉来自IO线程的Worker线程.工作线程将使用"ThreadPoolWorkQueue.Dispatch"启动其托管调用堆栈,而IO线程将使用"_IOCompletionCallback.PerformIOCompletionCallback"启动其托管调用堆栈.这是可以随时更改的所有实现细节,但在调试托管代码时了解您正在处理的内容会很有帮助.