Wednesday, 10 April 2019

IOCP and Tasks

When years ago I read this so detailed article about the low level internals of async calls in Windows I was fascinated. I still think that the article is essential, but after reading more on the topic I would say there is a small inaccuracy in it, related to its main statement "There is no Thread".

After reading a bit about IOCP and Managed Thread Pools, I came across this question and its excellent answer, that adds some additional wisdom to the initial article.

There is no actual user mode thread from your process involved in any of this operation (beyond just the initial asynchronous call). If you want to act on the fact that your data has arrived (which I assume you do when you're reading from a socket!), then you have to dequeue the completed items from your IOCP.

The point of IOCPs is that you can bind thousands of IO handles (sockets, files, ...) to a single IOCP. You can then use a single thread to drive those thousands of asynchronous processes in parallel.

Yes, that one thread doing the GetQueuedCompletionStatus is blocked while there is no completion pending on the IOCP, so that's probably where your confusion came from. But the point of IOCPs is that you block that one thread while you can have hundreds of thousands of network operations pending at any given time, all serviced by your one thread. You would never do a 1-to-1-to-1 mapping between IO handle/IOCP/Servicing Thread, because then you would lose any benefit from being asynchronous, and you might as well just use synchronous IO.

So I'll try to summarize how I understand it. When your code invokes and asynchronous I/O operation and you await for the returned Task, there will be a thread waiting for the Task to complete, but it's a Thread from the I/O ThreadPool, so it's not a new thread created just for this specific operation, but an existing thread from the pool that is being reused (well, obviously if it's the first usage of the IO Thread Pool by the process, the thread does not exist yet and has to be created, but then it will be reused multiple times). The "There is not thread" really means "There is not a new thread specific just to this operation". When the I/O operation is completed a message is posted to the IOCP and an I/O Thread will pick up the message. If the code that had launched the async operation had a SynchronizationContext, this will have been captured by the Task, and then this I/O Thread will dispatch the execution (of the continuation) to this SynchronizationContext, that in turn will use the original thread that had started the operation to run the continuation, else, the I/O Thread itself will run the continuation (the "asynchronous callback" as it's also called).

We can know if one thread belongs to the ThreadPool (Thread.CurrentThread.IsThreadPoolThread) or not, but it seems like there's no way to know if if it belongs to the Worker Thread Pool or the I/O Thread Pool.

No comments:

Post a Comment