Magnus Rahl
Jan 20, 2012
  28559
(1 votes)

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 %>ms
    with 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:

async_web_request_test

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.

Jan 20, 2012

Comments

Please login to comment.
Latest blogs
Integrating Searchspring with Optimizely – Part 1: Architecture & Setup

Integrating Searchspring with Optimizely – Part 1: Architecture & Setup

Wiselin Jaya Jos | Mar 20, 2026 |

CMS 13 Preview 4 — Upgrading from Preview 3

This is the third post in a series where I use the Alloy template as a reference to walk through each CMS 13 preview. The first post covered...

Robert Svallin | Mar 20, 2026

The move to CMS 13: Upgrade Notes for Technical Teams

A technical walkthrough of CMS 13 preview3 and headless work: what is changing, where the risks are, and how an upgrade and what to expect

Hristo Bakalov | Mar 20, 2026 |

Customizing Product Data Sent to Optimizely Product Recommendations in Optimizely Commerce

A practical guide to customizing IEntryAttributeService in Optimizely Commerce so you can override product titles, add custom feed attributes, and...

Wojciech Seweryn | Mar 20, 2026 |

A Synonyms Manager for Optimizely Graph

If you’re using Optimizely Graph for search, synonyms are one of the simplest ways to improve relevance without touching content. But they’re also...

Pär Wissmark | Mar 17, 2026 |

Building a Better Link Validation Report in Optimizely CMS 12

Broken links frustrate visitors and damage SEO. I have made a custom broken links report, that makes it easier to work broken links than the built-...

Henning Sjørbotten | Mar 17, 2026 |