An interesting question came up during my talk on the new asynchronous programming model in C# 5.0. Doesn’t an asynchronous operation mandate a new thread? Isn’t the actual work done on a new thread? If so, then the benefits of an asynchronous programming model is in UI scenarios only, so that the UI remains responsive. But in server scenarios, if a another thread is created to do the “actual” work, then there is no gain at all, probably even loss, as a new thread is created with no obvious benefits.
The short answer is maybe. An asynchronous operation may use another thread to do the actual work, but that is only in CPU intensive work. Consider the following, synchronous call:
var wc = new WebClient();
var result = wc.DownloadString("http://msdn.microsoft.com");
The client calls a synchronous method. What does the thread do while the call is in progress? Does that thread actually do the downloading? Not really. Looking at the CPU usage of this thread while the call is in progress would yield no activity. So, how is the result obtained?
Let’s see how this call to DownloadString might be processed. The call needs to make a network connection and get some data. It stands to reason that the network card on the machine must send some request to the server to get the data. Suppose that it does. Now what? What code needs to run on the machine at this time? The answer is – no code at all. The hardware did its thing – sent the request; and this was done by the calling thread. But that thread has nothing else to do at this time. In fact, it returns pretty quickly (after the network device driver finished programming the network card – a quick operation). The kernel I/O manager does the actual wait of that thread (because the call is synchronous).
What happens next? The network card does nothing regarding that call. When the data finally arrives, a hardware interrupt is generated, causing the NIC driver to execute its Interrupt Service Routine (ISR). But which thread executes that code? In Windows, there is no dedicated thread for handling interrupts. The ISR is handled by the current executing thread on the CPU selected to handle the interrupt. This may be any thread (it may be the idle thread if no special code is executing). It has no predictable relation to the original caller. You may be wondering how is a random thread interrupted in mid flight, causing a jump to kernel mode and execution of an ISR. The details are unimportant for this discussion. For more information, look for the IRQL (Interrupt Request Level) concept in the WDK docs.
The ISR code takes note of the fact that the data has arrived, and using the I/O manager, wakes up the original caller, which receives the result.
In this scenario, no additional threads were created. This is true of typical I/O requests as well. If we turn the operation to asynchronous like so:
var wc = new WebClient();
wc.DownloadStringCompleted += OnDownloadComplete;
The calling thread is transferred to kernel mode, calls the NIC driver (through the I/O manager) and returns almost immediately. This time the I/O manager does not wait, but releases the thread, freeing it to do something else, such as handling UI messages or servicing another server request. Again, no thread is created. When the network request is satisfied, some random running thread is used to service the interrupt, eventually posting a message to the UI thread message queue (assuming UI is involved). That thread raises the DownStringCompleted event. This is done behind the scenes using a SynchronizationContext, and again, not too important for this discussion. No threads are created.
To conclude, I/O and network operations are not CPU bound, and so don’t require any extra threads. CPU intensive work will require threads, in in this case asynchronous calls are not particularly beneficial in server scenarios. In UI scenarios, they are important to keep the UI responsive.