Async Pages part 2: How to use asynchrony in your Pages
This is a follow-up on the post How async pages may save your (server’s) life. In that post you may learn a bit more about how ASP.NET processing works and how your site can become incredibly slow even though your server isn’t busy (in the CPU load sense).
That post also described how you could get pre-.NET 4 sites to process a little better by setting the system.web/applicationPool maxConcurrentRequestsPerCPU attribute in your aspnet.config.
In this post we put some code to work to really solve the problem of requests blocking the queue.
Use async to change mode of processing
So how do you really solve the blocking problem? Say hello to the async Page attribute and it’s friends.
By putting async=”true” in your aspx:s Page directive you signal to ASP.NET that it can do asynchronous processing. What ASP.NET will then do is process your page to the PreRender stage and then look if you registered any async tasks. If you did, the thread will put your request back on a queue and go process another request. When your async task completes your request will be picked up by a thread again (not necessarily the same thread) and be processed to completion and the result sent to the client.
Async processing of UserControls
You can do async processing in UserControls as well as Pages as long as the hosting Page has its async attribute set to true.
Example UserControl using async processing
Consider the following UserControl used for demonstrating the async calls:
<%@ Control Language="C#" AutoEventWireup="false" CodeBehind="UrlStatus.ascx.cs" Inherits="EPiServer.Templates.AsyncControls.UrlStatus" %>
<p>Request to <%= Server.HtmlEncode(Url) %> started on thread <%= StartThread %>and completed on thread <%= EndThread %> in <%= ExecutionTime %>mswith status code <%= StatusCode %>. This was rendered on thread <%= System.Threading.Thread.CurrentThread.ManagedThreadId %>.</p>
As you can see it has a number of properties of which Url is a public property set from the page. StartThread, EndThread, ExecutionTime and StatusCode are used to store values demonstrating the async process.
Setting up the async processing
In the codebehind are of course the mentioned properties and also some fields used in the following methods. First the OnLoad method override:
protected override void OnLoad(EventArgs e){base.OnLoad(e);
// Register an asynchronous operation which will begin after PreRender
// stage and complete before PreRenderComplete
Page.AddOnPreRenderCompleteAsync(BeginAsyncOperation, EndAsyncOperation);}
This simply says: When PreRender is done, initiate the asynchronous processing by calling BeginAsyncOperation and later use EndAsyncOperation to process the result. The parameters are actually delegates, but that is inferred from the method names by the compiler.
Performing the async call
The BeginAsyncOperation looks like this:
private IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state){// Save the current thread and time for demonstration purposes
StartThread = System.Threading.Thread.CurrentThread.ManagedThreadId;_requestStartTime = DateTime.Now;_request = (HttpWebRequest)WebRequest.Create(Url);// With this return the runtime may stop processing the request and use the
// thread to service another request until this operation completes
return _request.BeginGetResponse(cb, state);
}
The StartThread property used in the view is set and the time is recorded in a member. As you can see, another member, _request, is used to store the request we create for the URL in the Url property. The reason for this is that we need it later to retrieve the result (the response) when the async call completes. That is done in EndAsyncOperation which looks like this:
private void EndAsyncOperation(IAsyncResult ar){// When this method is called, it means that the asynchrounous
// call is completed. Record the thread and time again
EndThread = System.Threading.Thread.CurrentThread.ManagedThreadId;var requestEndTime = DateTime.Now;var response = (HttpWebResponse)_request.EndGetResponse(ar);// Calculate the execution time and set the status code for rendering
ExecutionTime = (requestEndTime - _requestStartTime).TotalMilliseconds;StatusCode = response.StatusCode.ToString();// Sometime after this control is handled to a thread that does
// the rendering of the page (could be different from this thread)
}
Again, some of the properties used in the view are set. And as you can see the IAsyncResult object we returned from BeginAsyncOperation is passed back to us here. But when this method is called we are no longer necessarily running on the same thread (more probably we’re not). We use the _request field to retrieve the response and set the final property to display to it’s status code.
Understanding what happened
To try to make sense of this, Imagine the UserControl is registered in a Page (which has async=”true”) and used like this:
<h2>Async web request test</h2><Async:UrlStatus runat="server" Url="http://world.episerver.com/" />
The result can look like this:
Note that at least the start and end thread may sometimes be the same, it’s simply a matter of which thread is free when the async call completes.
Now, remember that when thread 7 had finished it’s setup of the async call it left the request sitting in memory and went to pick up another request (if there was one), then another and so on.
At some time our code’s request to EPiServer World completes and is handed over from the native code IP sockets or whatever, to thread 15 which enters our code and lets us process the result. Then it puts the request back on the queue where thread 8 (which up until now has also been serving other requests) picks up where thread 7 left to render the page and send it to the client.
(A bit) more advanced examples
Move on to the next blog post: Async Pages with databinding and events for more examples.
Comments