I see discussions about asynchronous web pages pop up more and more often these days. I attribute it to the anticipation that ASP.NET 2.0 makes this asynchronous hoopla easier to deal with. Let’s quickly review when asynchronous pages aren’t a good idea after all.
Some advocates of asynchronous handling of ASP.NET requests see it as the next best thing to improve performance of web applications. This claim holds true only in certain scenarios, though.
When a request to service a page comes in, it’s handled on a thread pool thread (a bit of funny terminology there for ya). If you hold on to it too long, you hit a page timeout. You can jack up the timeout, but you only hurt performance because the number of threads in the thread pool is limited, and the longer you hold on to one, the fewer requests your web app can service.
An ideal situation for an asynchronous call is when you have a “fire and forget” operation. You draw a new thread from the thread pool thread and delegate this long-running operation to it, and at the same time you let go of the thread processing the web request and let it complete. Say, a time consuming import or export operation is a good candidate.
Here’s another situation. You read a giant dataset and then do some transformations on it. Suppose all this takes too long, you exceed the timeout and your request gets recycled. Your users see a “white page” due to the timeout. It is tempting to take a thread from the thread pool and have it perform the looooooong read, but the thread—on which the request came—would have nothing to do. It’ll have to wait. So now you’re wasting 2 threads (!), not one. Asynchronous processing backfired.
To my point, here’s a valuable advice offered by the fabulous book Improving .NET Application Performance and Scalability (Chapter 5, page 217):
Avoid asynchronous calls that will block multiple threads for the same operation. The following code shows an asynchronous call to a Web service. The calling code blocks while waiting for the Web service call to complete. Notice that the calling code performs no additional work while the asynchronous call is executing.
[… sample code follows (see below) …]
When code like this is executed in a server application such as an ASP.NET application or Web service, it uses two threads to do one task and offers no benefit; in fact, it delays other requests being processed. This practice should be avoided.
The code in question is this:
// get a proxy to the Web service
customerService serviceProxy = new customerService ();
//start async call to CustomerUpdate
IAsyncResult result = serviceProxy.BeginCustomerUpdate(null,null);
// Useful work that can be done in parallel should appear here
// but is absent here
// wait for the asynchronous operation to complete
// Client is blocked until call is done
result.AsyncWaitHandle.WaitOne();
serviceProxy.EndCustomerUpdate(result);
How Do You Deal With Long Running Operations?
One way is to design some kind of a broker, have it queue calls and let it process them as threads in the thread pool become available. Check out How To: Submit and Poll for Long-Running Tasks.
If you still need to show the user a response immediately and can’t afford a please-wait page, then you’re on your own. If the culprit is a 10-join database read and your database is highly normalized, then think about somewhat denormalizing it so you can perform fast reads from a flattened structure. Another suggestion: consider message queuing.
In any case the final answer is: it depends. :) If this discussion went over your head, I want you to walk away with this much:
Avoid asynchronous calls that do not add parallelism.