|
Build More Scalable Sites
Reduce the chances of generating the dreaded 503 error—"Site Too Busy"—by implementing asynchronous processing with ASP.NET 2.0.
by Peter Vogel
May 18, 2006
Technology Toolbox: Visual Basic, ASP.NET, XML
Most business applications are data driven.
Your ASP.NET page sits and waits until the data comes back from the remote process whenever your code reads data from a database server or calls a Web Service on another server. This means that most ASP.NET pages in business applications spend a lot of time doing nothing. Your page is successfully performing one task during this time: It is tying up a thread from your application's thread pool. When enough pages do nothing, you exhaust the threads in your server's thread pool, and new visitors to your site must wait until a thread becomes available. At best, this results in longer response times; at worst, users start getting "Server too busy" errors and are denied access to the site.
I measured the effects of tying up a thread by creating a test client that issued 50 simultaneous requests to an ASP.NET page that, in turn, called a Web Service. The average response time for those 50 requests was more than eight seconds as new requests waited for threads tied up by old requests to become available.
The solution is to implement asynchronous processing. Doing so means your page's thread is returned to the thread pool, so another page can use it while your own code waits for results. When the remote server returns its results, a new thread picks up from where your page left off. The average response time for a new set of 50 calls by the client fell to less than two seconds when I switched my test page to asynchronous processing.
Some .NET functions support asynchronous processing out of the box. For example, the proxy class that .NET generates for a Web Service includes Begin and End versions of every method in the service that support asynchronous processing. Unfortunately, the Web environment makes asynchronous processing difficult. By the time that the remote server returns its results, your page may have finished executing, rendered the HTML to be sent to the user, and sent the page on its way to the client. In other words, it might be too late to affect the contents of a given page by the time the results are returned. Using asynchronous processing effectively means that you need some way to hold your page in memory—without, of course, tying up a thread.
Fortunately, taking advantage of asynchronous processing in ASP.NET makes it easy to exploit the asynchronous features built into the .NET Framework, while ensuring that your page waits for the remote server to return. Note that the response time in my test case improved, but asynchronous processing isn't a tool for speeding up your application in all situations. The improvement in response time is a side effect of throwing threads back into the pool when a client had nothing else to do. My response time improved because I had other clients waiting for a thread. Asynchronous processing actually increases the demand on your server's CPU because additional cycles must be spent starting and stopping threads. You should implement asynchronous processing only if you are in danger of running out of threads because asynchronous processing adds a small amount of overhead to your application and can degrade performance marginally.
Wire Up Asynchronous Processing
Using asynchronous page processing requires that you first enable asynchronous processing, then add the code that wires up your application logic to the page's asynchronous processing framework. Simply set the Async attribute in the Page directive to true in Source view to enable asynchronous processing for a page:
<%@ Page Async="true" ...
You can begin wiring your application code into the asynchronous processing by defining two routines that hold your code: one routine to call the long running process (the "Begin routine") and one routine to catch and process the results (the "End routine"). The sample I describe in this article calls a Web Service in the Begin routine (CallWebService) and catches the results of the Web Service in the End routine (ProcessWebServiceResults). You must declare these routines with specific signatures to integrate with ASP.NET's asynchronous processing:
Function CallWebService(ByVal sender As Object, _
ByVal e As System.EventArgs, _
ByVal cb As System.AsyncCallback, _
ByVal extraData As Object) As System.IAsyncResult
End Function
Sub ProcessWebServiceResults(ByVal ar As System.IAsyncResult)
End Sub
Back to top
|