When we started looking at how we could use the thread pool for asynchronous work, I (only!) mentioned three options:
- use the pool to run a task when IO completes;
- run a (possible recurrent) task at a specific time;
- execute a task when a kernel object is signaled.
There is still another option: you can also rely on the thread pool’s I/O completion support and write code that runs a callback – aka completion callback – on a thread from the pool in response to the end of an asynchronous I/O operation.
The CLR pool maintains a single I/O completion port per process. I/O completion ports were introduced in windows to improve scalability. The docs say that “I/O completion ports provide an efficient threading model for processing multiple asynchronous I/O requests on a multiprocessor system”. Even though I/O ports are a powerful concept, in managed code you’ll probably use them for supporting asynchronous I/O.
Let’s concentrate on files (I believe this is the most common case)…The idea is the following: you start by opening a file for an asynchronous operation and bind the handle of that file to the process wide completion port (notice that you can bind several handles to the same completion port). After doing that, you can simply start the required asynchronous operation and keep doing other interesting things (since you’ve started an asynchronous operation, the thread from where you started the async read or write won’t block).
Whenever that operation completes, it will queue a packet on that I/O completion port. The port will then proceed and use one of the thread pool’s thread to run the callback you’ve specified. The greatest advantage of using the thread pool for doing this kind of thing is that it already manages its own completion port and you don’t have to worry about the creating the “correct” number of threads that should be waiting on the port for the completion packets (ie, you’re free of having to manage the port itself and the threads that wait on it – not sure on what you think, but it does sound good to me ,,).
Even though you can write the “low-level” code for doing this kind of stuff on managed code (notice the irony of calling this interaction “low-level” code), the truth is that in most scenarios you’ll end up using the asynchronous IO APIs introduced on the .NET framework since they’re integrated with the pool.
Anyway, it would be probably a good idea to take a look at same code before using the high level version calls. Fortunately for me, someone else has already posted some demo code online and this means that I will simply redirect you to that interesting post.
And I guess it’s all for now. Keep tuned for more on multithreaded applications.