The .NET / .NET Core Thread Pool uses two different categories of threads internally: worker threads and I/O Completion Port (IOCP) threads. Both are just usual managed threads, but used for different purposes. Via different APIs (e.g. Task.Start
or ThreadPool.QueueUserWorkItem
) I can start CPU-bound async operations on the worker threads (which shouldn't block, otherwise the Thread Pool would probably create additional worker threads).
But what about performing I/O-bound asynchronous operations? How do the IOCP threads behave exactly in these situations? Specifically, I have the following questions:
- If I start an async I/O operation (e.g. for file, pipe, or network), I suspect that the current thread dispatches the async request. I also know (via the book "CLR via C#") that the CLR registers to an I/O completion port that is used to perform overlapped async I/O. I suspect that this IOCP is bound to the async operation so that it can queue the async operation result to the Thread Pool later. Thus, is my assumption correct that no IOCP thread is touched when an async request is started?
- I suspect that when the result of the async I/O operation is reported via the I/O completion port of the CLR, this is the place where IOCP threads come into place. The result is queued to the Thread Pool and an IOCP thread is used to handle it. However, when reading through some forum threads like this one on MSDN, I get the feeling that IOCP threads are actually used to dispatch the request and then block until the result is back. Is this the case? Are IOCP threads blocking while the I/O operation is handled by the opposing system?
- What about
async await
and SynchronizationContext
? Does an IOCP thread handle the async I/O response and then e.g. queue the continuation on the UI thread (assuming that ConfigureAwait(false)
is not called)?
- What about .NET Core on Linux / MacOS X? There are no I/O completion ports - are they emulated in any kind of way?
Damien and Hans pointed me into the right direction in the comments, which I want to sum up in this answer.
Damien pointed to Stephen Cleary's awesome blog post which answers the first three points:
- The async I/O operation is dispatched on the calling thread. No IOCP thread is involved.
- Consequently, no IOCP threads block during async I/O.
- When the result is returned to the .NET application, an IOCP thread is borrowed to mark the task complete. The continuation is queued to the target
SynchronizationContext
or the thread pool.
Hans pointed out that there are similar mechanisms to IOCP in Linux (epoll) and
MacOS (kqueue).