ASP.NET VERSIONS: 2.0
Don t Worry, Be Asynchronous
Asynchronous Operations Are Good for Users, but May Be Challenging for Developers
By Dino Esposito
Asynchronous operations in ASP.NET are essential for the overall performance of an application, but require ad hoc programming tools to be coded safely and successfully. No made-to-measure support was built in to ASP.NET 1.x; however, both ASP.NET 2.0 and ASP.NET Atlas bring something to the table. In the end, which ASP.NET version of the past, present, or future should developers use to build asynchronous operations in their pages? The simple answer is, the next. A more articulated answer, though, is still possible today and spans some of the features you can find in ASP.NET 2.0 and the work-in-progress of ASP.NET Atlas. This article discusses the details. But first and foremost, it draws a line and neatly separates server-side asynchronous operations from client-side requests that go asynchronously with respect to the current page.
Is It Async to You?
An operation is said to be asynchronous when it develops and completes without blocking the flow of the caller module. The canonical scenario for illustrating async tasks are database commands. At some point in the code you prepare an ADO.NET call, open the connection, set up the command, and execute it. Your code stops and waits until the results of the command are ready for use. After that, the code execution resumes with the first instruction following the database command. This way of working is called synchronous. So what is asynchronous? Speaking abstractly, if you execute the database command on a new and separate thread, you make it go asynchronously because the main thread and the new one proceed concurrently on distinct CPUs or in time-sharing, depending on the installed hardware and configuration.
When you leave the abstract world of theory to get into practice, a number of issues arise. From a pure ASP.NET perspective, the key issues are:
- Server-side async operations
- Wait screens
- Async client requests
Let s examine each of these aspects.
Server-side Async Operations
The ASP.NET HTTP runtime processes ASP.NET pages on the server and generates some markup. Next, the markup is incorporated in the HTTP response and served to the requesting browser. This means that there s one software module the browser waiting for some results while the page executes code on the Web server machine. For the browser (and the user sitting in front of it), the page request is synchronous and blocking. They just wait for the next page to download and materialize. They are blocked until something meaningful gets back from the opened socket.
On the server, the HTTP runtime allocates a pooled thread to serve the latest request. The thread a limited system resource sets up a handler for the request and waits for the markup to be generated. The thread also is blocked as the handler works synchronously while it executes all the code in the page class.
When discussing async operations, it is important to have a clear understanding of the two-folded nature of the problem. Do you want it to be async on the server and/or the client? Let s tackle the server side first.
In a fully synchronous model the default in ASP.NET if the page class contains lengthy operations (i.e., long-running database transactions, network requests, etc.), the pooled thread is blocked and so is the browser. Using the default synchronous model for potentially long, non-CPU-bound operations puts the scalability first, and functioning next, at serious risk. In high-traffic sites, the application may eventually become unresponsive as the number of free pooled threads available to serve new requests approaches zero.
The HTTP runtime, therefore, keeps getting new requests from connected clients. Each new request is assigned to a pooled thread a finite set of system resources. If too many pooled threads are synchronously waiting for some markup, and there are no available threads, the request is queued and the application inevitably slows down.
ASP.NET pages that execute long-running server-side operations should be configured to use asynchronous HTTP handlers. Mapped on a pooled thread, an async handler returns immediately after starting the potentially lengthy task and releases the pooled thread so that it can be used to serve new incoming requests. When the ongoing operation is complete, and related markup can finally be generated for the pending request, a sort of fake request is queued for the originally requested resource and mapped to the first available pooled thread for completion. The diagram in Figure 1 illustrates the difference between synchronous and asynchronous handlers.
Figure 1: Synchronous handlers vs. asynchronous handlers.
The asynchronous execution model for ASP.NET pages has been fully supported since ASP.NET 1.0. Its implementation, though, requires a fair amount of non-trivial code and good familiarity with the async pattern in the .NET Framework. In the end, it consists of writing each page with async operations as a custom HTTP handler that implements the IHttpAsyncHandler interface rather than IHttpHandler. In ASP.NET 2.0, things go smoothly and there are a number of facilities that greatly simplify the development.
You mark the asynchronous ASP.NET page with a new attribute in the @Page directive:
<%@ Page Async="true" ... %>
When the Async attribute is used, the page parser makes the dynamically created page class implement the IHttpAsyncHandler interface instead of IHttpHandler. Next, you register a pair of methods to handle the PreRenderComplete event in the page lifecycle. You use the following code, usually from Page_Load:
AddOnPreRenderCompleteAsync is a new method in the Page class and registers asynchronous handlers for the PreRenderComplete event. An asynchronous event handler consists of a Begin/End pair of event handler methods. The BeginEventHandler and EndEventHandler are delegates, defined as follows:
void EndEventHandler(IAsyncResult ar)
The net effect is that the request for an async page is split in two before and after the PreRenderComplete event (again, see Figure 1). The handler proceeds as usual until PreRenderComplete. Then it executes the registered method (BeginTask in the example). This method is expected to start the lengthy operation and return an object of type IAsyncResult. In Figure 2 you see a typical implementation of a BeginTask method. The returned IAsyncResult object represents the logical ticket that will allow the system to resume and complete the request in the second round. The IAsyncResult object can be a user-defined class that implements the interface or an object returned by .NET Framework classes that implement the asynchronous pattern. In Figure 2, you return the async object prepared by BeginGetResponse. Similar objects are returned by all BeginXXX methods in .NET 2.0, including, for example, BeginExecuteReader to execute ADO.NET queries asynchronously. An internal system component keeps track of pending operations and associates them with a system callback function. The callback function kicks in when the async task is completed and posts a new partial request for the same page that begins past the PreRenderComplete event. The two parts of the same requests may be served by distinct threads from the ASP.NET pool.
IAsyncResult BeginTask(object sender,
EventArgs e, AsyncCallback cb, object state)
// Prepare to make a Web request for the RSS feed
req = WebRequest.Create(RSSFEED);
// Begin the operation and return an IAsyncResult object
return req.BeginGetResponse(cb, state);
void EndTask(IAsyncResult ar)
// This code will be called on a pooled thread
using (WebResponse response = req.EndGetResponse(ar))
using (reader = new
text = reader.ReadToEnd();
// Process the RSS data
Figure 2: Typical implementation of a pair of async page methods.
Async handlers only help the ASP.NET runtime to optimize the use of pooled threads and keep the application more responsive and scalable. It doesn t have any effect on the client. In other words, the connected user still waits in front of a frozen user interface for the time it takes to complete the two parts of the request. For the user, nothing really changes whether the request goes synchronously or asynchronously. Any potentially lengthy operation, though, should be signaled to users via a progress bar or, perhaps, a little animation. It is important to note that this is all another problem that has no relationship with the server-side async pattern.
If you foresee that users will experience some delay after they click a button in an ASP.NET page, and want to be friendly enough, you can resort to a simple trick, as shown here: