After reading this article which states :
After a device finishes its job , (IO operation)- it notifies the CPU via interrupt.
... ... ...
However, that “completion” status only exists at the OS level; the process has its own memory space that must be notified
... ... ...
Since the library/BCL is using the standard P/Invoke overlapped I/O system, it has already registered the handle with the I/O Completion Port (IOCP), which is part of the thread pool.
... ... ...
So an I/O thread pool thread is borrowed briefly to execute the APC, which notifies the task that it’s complete.
I was interesting about the bold part :
If I understood correctly , after the the IO operation is finished , it has to notify to the actual process which executed the IO operation.
Question #1:
Does it mean that it grabs a new thread pool thread for each completed IO operation ? Or is it a dedicated number of threads for this ?
Question #2:
Looking at :
for (int i=0;i<1000;i++)
{
PingAsync_NOT_AWAITED(i); //notice not awaited !
}
Does it mean that I'll have 1000 IOCP threadpool thread simultaneously ( sort of) running here , when all are finished ?
This is a bit broad, so let me just address the major points:
The IOCP threads are on a separate thread pool, so to speak - that's the I/O threads setting. So they do not clash with the user thread-pool threads (like the ones you have in normal await
operations or ThreadPool.QueueWorkerItem
).
Just like the normal thread pool, it will only allocate new threads slowly over time. So even if there's a peak of async responses that happen all at once, you're not going to have 1000 I/O threads.
In a properly asynchronous application, you're not going to have more than the number of cores, give or take, just like with the worker threads. That's because you're either doing significant CPU work and you shold post it on a normal worker thread or you're doing I/O work and you should do that as an asynchronous operation.
The idea is that you spend very little time in the I/O callback - you don't block, and you don't do a lot of CPU work. If you violate this (say, add Thread.Sleep(10000)
to your callback), then yes, .NET will create tons and tons of IO threads over time - but that's just improper usage.
Now, how are I/O threads different from normal CPU threads? They're almost the same, they just wait for a different signal - both are (simplification alert) just a while
loop over a method that gives control when a new work item is queued by some other part of the application (or the OS). The main difference is that I/O threads are using IOCP queue (OS managed), while normal worker threads have their own queue, completely .NET managed and accessible by the application programmer.
As a side note, don't forget that your request might have completed synchronously. Perhaps you're reading from a TCP stream in a while loop, 512 bytes at a time. If the socket buffer has enough data in it, multiple ReadAsync
s can return immediately without doing any thread switching at all. This isn't usually a problem because I/O tends to be the most time-intensive stuff you do in a typical application, so not having to wait for I/O is usually fine. However, bad code depending on some part happenning asynchronously (even though that isn't guaranteeed) can easily break your application.