March 13th, 2009

Making an asynchronous call in ASP.NET on synchronous pages

If you need to make an asynchronous method call from an ASP.NET page the usual technique is to use asynchronous pages to allow lengthy operations to execute without holding up the currently executing thread.

However, there are scenarios where it will not be possible to implement an asynchronous page. This affected me most recently when I had a large data store sitting in the ASP.NET cache that was being refreshed from an incredibly slow web service call. If the data drops out of the cache then it needs to be refreshed, yet this could happen on any page in the application. Added to this was the fact that this was a CMS integration project where I did not have control over any of the pages in the administration interface for the CMS.

So, asynchronous pages were out. One strategy is, of course, to change the way you store and refresh the data store in the cache through a local caching mechanism of last resort. However, it would be preferable to find a way of sending off an asynchronous refresh request if at all possible.

The code below shows how it’s done. The helper class below implements a "fire and forget" asynchronous call by taking a thread from the thread pool and firing it at a delegate.

public class AsynchronousCallHelper
{
    class TargetInformation
    {
        internal TargetInformation(Delegate dgte, object[] args)
        {
            Target = dgte;
            Args = args;
        }
        internal readonly Delegate Target;
        internal readonly object[] Args;
    }

    private static WaitCallback dynamicInvoker = new WaitCallback(DynamicInvoker);

    public static void FireAndForget(Delegate dgte, params object[] args)
    {
        ThreadPool.QueueUserWorkItem(dynamicInvoker, new TargetInformation(dgte, args));
    }

    static void DynamicInvoker(object obj)
    {
        try
        {
            TargetInformation target = (TargetInformation)obj;
            target.Target.DynamicInvoke(target.Args);
        }
        catch (Exception ex)
        {
            //* We have to resort to using Trace here as it is thread-safe
            System.Diagnostics.Trace.WriteLine(ex.ToString());
        }
    }
}

To implement the class, declare a delegate for the method that you want to call asynchronously and use the helper class to fire off a thread on it. The code sample below shows how this can be done on an ASP.NET page. The method MyMethod is called on a new thread while the page continues to execute and returns a response to the browser.

Care should be taken here because the new thread will not have any awareness of the orginal request context. If you want it to be able to access the context (and therefore the cache) then you will need to pass a reference to the context in with your delegate, as shown below.

public partial class _Default : System.Web.UI.Page
{
    //* This is the delegate called by the asynchronous web service call
    delegate void MyMethodDelegate(HttpContext context);

    protected void Page_Load(object sender, EventArgs e)
    {
        //* Call "MyMethod" asycnhronously
        AsynchronousCallHelper.FireAndForget(new MyMethodDelegate(MyMethod), HttpContext.Current);
        Response.Write("Finished");
    }

    protected void MyMethod(HttpContext context)
    {
        //* Send the thread to sleep to demonstrate that the ASP.NET page has been returned
        Thread.Sleep(10000);
    }
}