我正在尝试了解I / O完成端口,以及它们与使用async
- await
进行I / O相关的方式。
臭名昭着的文章There is No Thread谈到在I / O完成后短暂借用IOCP。因为本文的重点是要表明当花哨的硬件级I / O内容在飞行中时,没有一个线程被循环消耗,如
I / O已经完成了吗?没有.I / O已经完成了吗?没有.I / O已经完成了吗?没有...
然后我看着this article说了一个
“组件负责检查排队的完成端口 元素“
并举例如
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);
}
}
}
var completionPortThread = new Thread(() => new IOCompletionWorker().Start(completionPortHandle))
{
IsBackground = true
};
completionPortThread.Start();
对我来说,似乎有一些民意调查正在进行中。
我想我的问题归结为
答案 0 :(得分:5)
两篇文章都以自己的方式正确。
IOCP不是线程。它们可以被视为某种队列,其中内核(或者常规用户模式代码,通过PostQueuedCompletionStatus)可以发布完成项。没有与IOCP本身相关的固有线程模型或线程,它们只是多个生产者 - 消费者队列。
我们以网络套接字为例,但对于任何类型的异步工作都是如此:
在任何此操作中都没有涉及您的进程的实际用户模式线程(除了初始异步调用之外)。如果你想对你的数据到达这一事实采取行动(我假设你在从套接字读取时就这样做了!),那么你必须从你的IOCP中出列已完成的项目。
IOCP的重点在于您可以将数千个IO句柄(套接字,文件,...)绑定到单个IOCP。然后,您可以使用单个线程并行驱动数千个异步进程。
是的,执行GetQueuedCompletionStatus的一个线程被封锁,而IOCP上没有完成待处理,所以这可能是你的困惑所在。但IOCP的关键在于您阻止了一个线程,而您可以在任何给定时间处理数十万个网络操作,所有这些都由您的一个线程提供服务。你永远不会在IO句柄/ IOCP /服务线程之间进行1对1对1的映射,因为那样你就会失去异步的任何好处,你也可以只使用同步IO。
IOCP的主要目的是在Windows下实现令人印象深刻的异步操作并行性。
我希望这可以澄清混乱。
至于具体问题
纯托管异步操作(例如,使用Task.Delay执行async-await)不涉及任何IO句柄,因此它们最终不会被某个驱动程序发布到IOCP,因此这些操作会丢失在“工人”类别下。
作为旁注,您可以通过其callstack告诉来自IO线程的Worker线程。 工作线程将使用“ThreadPoolWorkQueue.Dispatch”启动其托管调用堆栈,而IO线程将使用“_IOCompletionCallback.PerformIOCompletionCallback”启动其托管调用堆栈。这是可以随时更改的所有实现细节,但在调试托管代码时了解您正在处理的内容会很有帮助。