Asynchronous Calls in a Web Page

November 2006

Overview

Do you have an ASP.NET page that performs a long-running (>100ms) I/O request, like making an HTTP request or executing a costly SQL command? Has your ASP.NET web application unexpectedly hit its maximum throughput? Do ASP.NET web requests start queuing when your web server is under significant load? If so this article may help you address these issues.

You of course want your web application to handle as many requests as possible. Theoretically you want the web server to scale perfectly with any increase in the volume of traffic. Of course this is not possible for many reasons. One of these reasons, the one we will be concerned with here, is that there is a pool of threads that are allocated for use by incoming ASP.NET requests. Under heavy load the web server could exhaust this pool which causes request queuing. Unless the heavy load is very brief this is very bad, more and more requests will queue exhausting the resources of the web server.

So, what do you do when your web server throughput appears to be maxed out? The easy solution is to buy another web server for the web farm. The other way is to reduce the execution time of the page so that the web request thread is released back into the pool as quickly as possible. Buying a server is fine if you have the money, and optimizing code is a great idea, but what if the page needs to do a costly I/O request and this request has already been optimized as much as possible?

To highlight why an I/O request inside a web request is a bad idea let's review this concept of the thread pool. Suppose the thread pool for ASP.NET requests contains 25 threads. Suppose that a web page contains an I/O request that takes 2 seconds to complete. In this scenario the maximum throughput for the page is 12.5 pages per second. If the web server receives 13 or more requests per second incoming requests will start to queue since all the threads in the pool will be busy.

The trick here is that for the most part the web request threads won't actually be busy. They will simply be blocking on the I/O call, waiting for the call to return. Wouldn't it be great if there was a way to release threads that are blocking on I/O calls back to the pool and when the request returns to allocate a different thread from the pool to complete the web request after the I/O request is finished? The good news is that in .NET, you can!

Asynchronous Calls to the Rescue

The first thing to note about most I/O bound calls in .NET is that there is typically a way to invoke them asynchronously. For instance the System.Data.SqlClient.SqlCommand object provides a method called ExecuteNonQuery that synchronously runs a database query. Also provided by this class are two methods that are used to do the same thing asynchronously BeginExecuteNonQuery and EndExecuteNonQuery. Similarly, the System.Net.HttpWebRequest class provides a GetResponse method for sychronous operations and BeginGetResponse and EndGetResponse methods for asynchronous operations.

The key to the asynchronous "Begin" methods is that they return an instance of System.IAsyncResult. This class is used by callback handlers to process the result when the asynchronous request has finished. If we were to use the asynchronous methods to perform the I/O during our web request we could release the web request thread back to the pool and increase our application's throughput. But before your go using these methods directly, ASP.NET (2.0+) provides handy ways to hook in these asychronous calls without having to write all the plumbing yourself.

In fact, ASP.NET provides multiple ways to use asychronous calls to prevent thread blocking. These include:

Each class serves a specific purpose but the class that is easiest to use and provides flexibility when dealing with multiple asynchronous requests is System.Web.UI.PageAsyncTask.

PageAsyncTask

The System.Web.UI.PageAsyncTask makes asynchronous calls easy. There are really only two steps to using this class. The first is to instantiate an instance of it and the second is register the instance with the System.Web.UI.Page (i.e. the codebehind class of the ASPX page). For example:

Page_Load()
{
  // create the asynchronous task instance
  PageAsyncTask asyncTask = new PageAsyncTask(
    new BeginEventHandler(this.beginAsyncWebRequest),
    new EndEventHandler(this.endAsyncWebRequest),
    new EndEventHandler(this.asyncWebRequestTimeout),
    null);
  
  // register the asynchronous task instance with the page
  this.RegisterAsyncTask(asyncTask);
}

The first three parameters of the constructor are delegates to methods that you must write begin and end the your asyncronous call. In the case of making an HTTP web request they might look like the following:

IAsyncResult beginAsyncWebRequest(
  object sender, EventArgs e, AsyncCallback callback, object state)
{
  HttpWebRequest request =
    (HttpWebRequest)WebRequest.Create("http://www.[something].com/");
    
  IAsyncResult result =
    (IAsyncResult)request.BeginGetResponse(callback, request);
    
  return result;
}

void endAsyncWebRequest(IAsyncResult result)
{
  HttpWebRequest request = (HttpWebRequest)result.AsyncState;
  HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
  ...
}

void asyncWebRequestTimeout(IAsyncResult result)
{
  HttpWebRequest request = (HttpWebRequest)result.AsyncState;
  request.Abort();  
}

Note: Most examples of the timeout callback function for a WebRequest task call the request's EndGetResponse method. This usually works fine but when the web server is under heavy load the call to EndGetResponse may hang for a few seconds, or more. This can cause performance issues and in my experience calling the Abort method ensures a quick return and proper clean-up.

Controlling PageAsyncTask

In order for PageAsyncTask to work at you should include the string "Async=True" in your ASPX's page's @Page directive. You can also control the timeout for the asychronous task with the "AsyncTimeout=x" parameter where the value for "x" should be in seconds. This timeout can also be controlled in code via the Page class' AsyncTimeout property. If you want to set the timeout for all asynchronous tasks in your web application you can put the following in your web.config file:

<system.web>
  <pages asyncTimeout="x" />
</system.web>

Normally, the "begin" method that was specified in the PageAsyncTask's constructor is called between the Page's PreRender and PreRenderComplete events. In fact the PreRenderComplete was introduced into ASP.NET 2.0 just for the purpose of doing work just after asynchronous events have completed. However you can request that asynchronous tasks happen at any time by calling the Page's ExecuteRegisteredAsyncTasks method.

The PageAsyncTask class has boolean property called ExecuteInParallel. To understand what this is for it's important to note that it is quite possible to register multiple asynchronous tasks for a single page. For example:

Page_Load()
{
  // create and register an asynchronous task for the web request
  PageAsyncTask asyncTask = new PageAsyncTask(
    new BeginEventHandler(this.beginAsyncWebRequest, ...))  
  this.RegisterAsyncTask(asyncTask);

  // create and register an asynchronous task for the database logging request
  asyncTask = new PageAsyncTask(
    new BeginEventHandler(this.beginLoggingRequest, ...))
  this.RegisterAsyncTask(asyncTask);
}

When multiple tasks are registered the ExecuteInParallel property can be used to determine if they are executed sequentially (in the order they were registered) or in parallel. It is very important to note the AsyncTimeout is for the the sum total of all asynchronous requests, so if ExecuteInParallel is set to "true" and two asychronous tasks are registered where each task could take up to 2 seconds, then AsyncTimeout should be set to at least 4 seconds.

Comments
by Mike Excellent article, many thanks.

Only one problem - for someone that is not very clued up on asynchronous tasks you let it down by not showing how to actually create the instances of the new PageAsyncTask object in your page_load handler:
....
PageAsyncTask asyncTask = new PageAsyncTask(
new BeginEventHandler(this.beginAsyncWebRequest, ...))
....

I can see from the constructor for BeginEventHandler that it needs an object, event args, a callback and another object (for "extraData" according to MSDN) but I don't understand where or how I'm meant to get these parameters from to create the asynchronous task.... I will probably work it myself but perhaps you should think about claryfing or extending this article slightly for newbies to asynchronous web tasks.
by Mike D'oh!!! I just realised when I re-read the article you *do* show how to create the PageAsyncTask object in the first example of page_load... my apologies and obviously you can ignore the comment above!
by kias very useful description
by llllll lllllll
by llllll lllllll
by llllll lllllll
by gata congrats
by gata works fine
by Richard Is there a way to wrap the beginAsyncWebRequest() stuff into a class that can be called repeatedly?

I need to call a web service (that takes a single string parameter) roughly 100 times every 5 minutes. I can't figure out how to pass the urls into beginAsyncWebRequest.

e.g. domain.com/service.asmx?this=myValue

I'm looking to do something like
foreach (String s in myStrings)
{
this.RegisterAsyncTask(asyncTask(s));
}
by synchronous process I am not able display any message when asynchronous task running....plz help me.......
by Sanjay Gupta Very good description about PageAsyncTask Object.
by Sanjay Gupta Great Article.
Add a Comment Name: Comment: Enter the text in the image below:
w3 enterprises