Ruminations of J.net idle rants and ramblings of a code monkey

ASP.NET Async Page Model

.NET Stuff | Performance | Web (and ASP.NET) Stuff

I just did a Code Clinic for the Second Life .NET User’s Group on using the ASP.NET async page model and it occurred to me that it’d be a good idea to do a little blog post about it as well. I’ve noticed that a lot of developers don’t know about this little feature and therefore don’t use it. It doesn’t help that the situations where this technique helps aren’t readily apparent with functional testing on the developer’s workstation or even on a separate test server. It only rears its head if you do load testing … something that few actually do (I won’t go there right now).

So, let me get one thing straight from the get-go here: I’m not going to be talking about ASP.NET AJAX. No way, no how. I’m going to be talking about a technique that was in the original release of ASP.NET 2.0 and, of course, it’s still there. There are some big-time differences between the async model and AJAX. First, the async model has nothing at all to do with improving the client experience (at least not directly, though it will tend to). Second, the async model doesn’t have any client-side goo; it’s all server-side code. And finally, there is no magic control that you just drop on your page to make it work … it’s all code that you write in the code-behind page. I do want to make sure that this clear ‘cuz these days when folks see “async” in relation to web pages, they automatically think AJAX. AJAX is really a client-side technique, not server side. It does little to nothing to help your server actually scale … it can, in some cases, actually have a negative impact. This would happen when you make additional round trips with AJAX that you might not normally do without AJAX, placing additional load on the server. Now, I’m not saying that you shouldn’t use AJAX … it’s all goodness … but I just want to clarify that this isn’t AJAX. Now, you can potentially this this for AJAX requests that are being processed asynchronously from the client.

Now that we have that out of the way, let me, for a moment, talk about what it is. First, it’s a really excellent way to help your site scale, especially when you have long-running, blocking requests somewhere in the site (and many sites do have at least a couple of these). Pages that take a few seconds or more to load may be good candidates. Processes like making web services calls (for example, to do credit card processing and order placement on an eCommerce site) are excellent candidates as well.

Why is this such goodness? It has to do with the way ASP.NET and IIS do page processing. ASP.NET creates a pool of threads to actually do the processing of the pages and there is a finite number of threads that will be added to the pool. These processing threads are created as they are needed … so creating additional threads will incur some overhead and there is, of course, overhead involved with the threads themselves even after creation. Now, when a page is requested, a thread is assigned to the page from the pool and that thread is then tied to processing that page and that page alone … until the page is done executing. Requests that cannot be serviced at the time of the request are then queued for processing as a thread becomes available. So … it then (logically) follows that pages that take a long time and consume a processing thread for extended periods will affect the scalability of the site. More pages will wind up in the queue and will therefore take longer since they are waiting for a free thread to execute the page. Of course, once the execution starts, it’ll have no difference on the performance … it’s all in the waiting for a thread to actually process the page. The end result is that you cannot services as many simultaneous requests and users.

The async page model fixes this. What happens is that the long running task is executed in the background. Once the task is kicked off, the thread processing the thread is then free to process additional requests. This results in a smaller queue and less time that a request waits to be serviced. This means more pages can actually be handled at the same time more efficiently … better scalability. You can see some test results of this on Fritz Onion’s blog. It’s pretty impressive. I’ve not done my own scalability testing on one of my test servers here, but I think, shortly, I will. Once I do, I’ll post the results here.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

How do you do this? To get started is actually quite easy, simple in fact. You need to add a page directive to your page. This is required regardless of which method you use (there are two). ASP.NET will then implement IAsyncHttpHandler for you behind the scenes. It looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Async="True" %>

Simple enough, right? Let me just add a couple of things that you need to make sure you have in place. You will need to follow the .NET asynchronous pattern for this to work … a Begin method that returns IAsyncResult and an end method that takes this result. It’s typically easiest to do this with API’s that already have this implemented for you (you just return their IAsyncResult object). There’s a ton of them and they cover most of the situations where this technique helps.

Now, to actually do this. Like I said, there’s two different ways to use this. The first is pretty easy to wireup and you can add multiple requests (I misstated this during the Code Clinic), but all of the async requests run one at a time, not in parallel. You simply call Page.AddOnPreRenderCompleteAsync and away you go. There are two overloads for this method, as follows:

void AddOnPreRenderCompleteAsync(BeginEventHandler b, EndEventHandler e)

 

 

 

 

 

 

 

 

 

 

 

 

void AddOnPreRenderCompleteAsync(BeginEventHandler b, EndEventHandler e, object state)

The handlers look like the following:

IAsyncResult BeginAsyncRequest(object sender, EventArgs e, AsyncCallback cb, object state)
void EndAsyncRequest(IAsyncResult ar)

 

 

 

 

 

 

The state parameter can be used to pass any additional information/object/etc. that you would like to the begin and the end methods (it’s a member if the IAsyncResult interface), so that can be pretty handy.

The code behind for such a page would look like the following:

protected void Page_Load(object sender, EventArgs e)
{
LoadThread.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 
AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginGetMSDN),
new EndEventHandler(EndAsyncOperation)); 
}
public IAsyncResult BeginGetMSDN(object sender, EventArgs e, AsyncCallback cb, object state)
{
BeginThread.Text =
Thread.CurrentThread.ManagedThreadId.ToString();HttpWebRequest_request = 
(HttpWebRequest)WebRequest.Create(@"http://msdn.microsoft.com");return _request.BeginGetResponse(cb, _request);
}
void EndAsyncOperation(IAsyncResult ar)
{
EndThread.Text =
Thread.CurrentThread.ManagedThreadId.ToString();string text;HttpWebRequest _request = (HttpWebRequest)ar.AsyncState;using (WebResponse response = _request.EndGetResponse(ar))
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
text = reader.ReadToEnd();
}
}
Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);MatchCollection matches = regex.Matches(text);StringBuilder builder = new StringBuilder(1024);foreach (Match match in matches)
{
builder.Append(match.Groups[1]);
builder.Append("<br/>");
}
Output.Text = builder.ToString();
}
}

If you run this (on a page with the proper controls, of course), you will notice that Page_Load and BeginGetMSDN both run on the same thread while EndAsyncOperation runs on a different thread.

The other method uses a class called PageAsyncTask to register an async task with the page. Now, with this one, you can actually execute multiple tasks in parallel so, in some cases, this may actually improve the performance of an individual page. You have two constructors for this class:

 

 

public PageAsyncTask(
BeginEventHandler beginHandler,
EndEventHandler endHandler,
EndEventHandler timeoutHandler,
Object state)

and

public PageAsyncTask(
BeginEventHandler beginHandler,
EndEventHandler endHandler,
EndEventHandler timeoutHandler,
Object state,
bool executeInParallel){}

 

The only difference between the two is that one little argument … ExecuteInParallel. The default for this is false, so if you want your tasks to execute in parallel, you need to use the second constructor. The delegates have identical signatures to the delegates for AddOnPreRenderComplete. The new handler timeoutHandler, is called when the operations times out and has the same signature to the end handler. So … it’s actually trivial to switch between the two (I did it to the sample listing above in about a minute.) I, personally, like this method better for two reasons. One, the cleaner handling of the timeout. That’s all goodness to me. Second, the option to have them execute in parallel. The same page as above, now using PageAsyncTask looks like to following:

public partial class _Default : System.Web.UI.Page{
protected void Page_Load(object sender, EventArgs e)
{
LoadThread.Text = Thread.CurrentThread.ManagedThreadId.ToString();PageAsyncTask t = new PageAsyncTask(
BeginGetMSDN,
EndAsyncOperation,
AsyncOperationTimeout,
false);
}
public IAsyncResult BeginGetMSDN(object sender, EventArgs e, AsyncCallback cb, object state)
{
BeginThread.Text =
Thread.CurrentThread.ManagedThreadId.ToString();HttpWebRequest_request = 
(HttpWebRequest)WebRequest.Create(@"http://msdn.microsoft.com");return _request.BeginGetResponse(cb, _request);
}
void EndAsyncOperation(IAsyncResult ar)
{
EndThread.Text =
Thread.CurrentThread.ManagedThreadId.ToString();string text;HttpWebRequest _request = (HttpWebRequest)ar.AsyncState;using (WebResponse response = _request.EndGetResponse(ar))
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
text = reader.ReadToEnd();
}
}
Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);MatchCollection matches = regex.Matches(text);StringBuilder builder = new StringBuilder(1024);foreach (Match match in matches)
{
builder.Append(match.Groups[1]);
builder.Append("<br/>");
}
Output.Text = builder.ToString();
}
void AsyncOperationTimeout(IAsyncResult ar)
{
EndThread.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 
Output.Text = "The data is not currently available. Please try again later."}
}

Not much difference there. We have 1 additional method for the timeout and the registration is a little different. By the way, you can pass null in for the timeout handler if you don’t care about it. I don’t recommend doing that, personally, but that’s up to you.

There you have it … a quick tour through the ASP.NET asynchronous page model. It’s clean, it’s easy, it’s MUCH better than spinning up your own threads and messing with synchronization primitives (this is mucho-bad-mojo, just say NO) and it’s got some pretty significant benefits for scalability.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

With that, I’m outta here. Happy coding!