Skip navigation

Don’t Worry, Be Asynchronous

Asynchronous Operations Are Good for Users, but May Be Challenging for Developers

CoreCoder

LANGUAGES: C#

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 (

   new BeginEventHandler(BeginTask),

   new EndEventHandler(EndTask)

);

 

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:

 

IAsyncResult BeginEventHandler(

   object sender,

   EventArgs e,

   AsyncCallback cb,

   object state)

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

 string text;

 using (WebResponse response = req.EndGetResponse(ar))

 {

   StreamReader reader;

   using (reader = new

    StreamReader(response.GetResponseStream()))

   {

       text = reader.ReadToEnd();

   }

   // Process the RSS data

   ProcessFeed(text);

 }

}

Figure 2: Typical implementation of a pair of async page methods.

 

Wait Screens

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:

 

 OnClientClick="ShowProgress();"

 OnClick="Button1_Click" />

 

When the users click the button, the page posts back and starts a lengthy task. The task can either be coded synchronously or asynchronously (it doesn t matter from the client-page perspective). The OnClientClick attribute registers some JavaScript code to execute before the page posts back. If you have a hidden label with a wait message, you can turn it on (the final effect is shown in Figure 3):

 

 :

 

      OnClientClick="ShowProgress();" OnClick="Button1_Click" />

 

      Text="Please, wait..." />

 

You should note that the script code you attach to the push button is limited in its capabilities. The most interesting thing you can do here is to display a static message with a bit of HTML frills, such as text formatting and styles. Animated GIFs, for example, won t work here. The reason is the same that makes it unnecessary to hide the progress label in the preceding code. As you can see, the wait message is displayed programmatically, but never hidden. Yet, as you see in Figure 3, the message disappears when the long-running operation terminates. When you push the button, you actually order a full page refresh. The wait message is the last thing that happens to the current page before it is frozen waiting for server response. An animated GIF requires the browser to periodically refresh the page to change frames. This can t just happen if the page is frozen. When the server operation returns, the page is redrawn from scratch and the wait message is wiped out.

 


Figure 3: Showing a wait message during a long-running operation.

 

Async Client Requests

The next step in the evolution of Web applications is enabling server operations from the client. The AJAX model is all about this executing server code in response to a client action, such as clicking a button. The client request is often executed in an asynchronous manner with a JavaScript callback function invoked when the task is complete, times out, or throws an exception.

 

From a user perspective, being able to execute remote calls from the client without causing a full page refresh is a big win, no matter if it goes synchronously or asynchronously. If asynchronous, the user can continue working with the rest of the user interface and check buttons, select items, and fill text boxes at will. Of course, a synchronous operation keeps the flow in the page frozen, the user interface little responsive; but the browser is still taking care of the page. For example, if you display an animated GIF, it will be iterated as usual during a client request.

 

In ASP.NET, the application of the AJAX model is delegated to Atlas a glimpse of the next version of ASP.NET. The most typical way of setting up a client asynchronous request in ASP.NET Atlas is through local Web services or page methods. A local Web service is an ASP.NET Web service hosted in the same application and server machine as the calling page. When you reference such a Web service in an Atlas page, the Atlas runtime builds a JavaScript proxy class and allows you to post requests programmatically. If you don t want to build a Web service public endpoints, SOAP interface, Web methods you can simply transform the host page in a Web service by decorating methods on the code-behind class with the WebMethod attribute. The net effect is the same; but in this case, the method is local to the application and is not public on the Internet. For more information on the Atlas platform you can visit http://atlas.asp.net or have a look at my book, Introducing Microsoft Code Name Atlas for AJAX Development, from Microsoft Press.

 

Conclusion

Asynchronous operations are good for users because they significantly lessen the typical frustration due to lack of feedback and signs of life during lengthy tasks. At the same time, asynchronous operations are good for developers because they contribute to helping maintain optimal levels of scalability with a smarter use of pooled threads. The problem is, how do you code async operations without incurring errors? In ASP.NET 2.0, you have a brand new set of tools to code asynchronous pages easily and effectively. In ASP.NET Atlas, you also have the possibility of controlling these operations from the client. In any case, be aware that asynchronous activity is essential at two distinct levels: application and user.

 

The sample code accompanying this article is available for download.

 

Dino Esposito is a Solid Quality Learning mentor and the author of Programming Microsoft ASP.NET 2.0 Core Reference and Programming Microsoft ASP.NET 2.0 Applications-Advanced Topics, both from Microsoft Press. Based in Italy, Dino is a frequent speaker at industry events worldwide. Join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish