Using the ASP.NET MVC Futures AsyncController

Last week, I blogged about all stuff that is included in the ASP.NET MVC Futures assembly, which is an assembly available on CodePlex and contains possible future features (tonguetwister!) for the ASP.NET MVC framework. One of the comments asked for more information on the AsyncController that is introduced in the MVC Futures. So here goes!

kick it on DotNetKicks.com

Asynchronous pages

The AsyncController is an experimental class to allow developers to write asynchronous action methods. But… why? And… what? I feel a little confusion there. Let’s first start with something completely different: how does ASP.NET handle requests? ASP.NET has 25 threads(*) by default to service all the incoming requests that it receives from IIS. This means that, if you have a page that requires to work 1 minute before returning a response, only 25 simultaneous users can access your web site. In most situations, this occupied thread will just be waiting on other resources such as databases or webservice, meaning it is actualy waiting without any use while it should be picking up new incoming requests.

(* actually depending on number of CPU’s and some more stuff, but this is just to state an example…)

In ASP.NET Webforms, the above limitation can be worked around by using the little-known feature of “asynchronous pages”. That <%@ Page Async="true" ... %> directive you can set in your page is not something that had to do with AJAX: it enables the asynchronous page processing feature of ASP.NET Webforms. More on how to use this in this article from MSDN magazine. Anyway, what basically happens when working with async pages, is that the ASP.NET worker thread fires up a new thread, assigns it the job to handle the long-running page stuff and tells it to yell when it’s done so that the worker thread can return a response to the user. Let me rephrase that in an image:

Asynchronous page flow 

I hope you see that this pattern really enables your web server to handle more requests simultaneously without having to tweak standard ASP.NET settings (which may be another performance tuning thing, but we will not be doing that in this post).

AsyncController

The ASP.NET MVC Futures assembly contains an AsyncController class, which actually mimics the pattern described above. It’s still experimental and subject to change (or to disappearing), but at the moment you can use it in your application. In general, the web server schedules a worker thread to handle an incoming request. This worker thread will start a new thread and call the action method on there. The worker thread is now immediately available to handle a new incoming request. Sound a bit the same as above, no? Here’s an image of the AsyncController flow:

AsyncController thread flow

Now you know the theory, let’s have a look at how to implement the AsyncController pattern…

Preparing your project…

Before you can use the AsyncController, some changes to the standard “File > New > MVC Web Application” are required. Since everything I’m talking about is in the MVC Futures assembly, first grab the Microsoft.Web.Mvc.dll at CodePlex. Next, edit Global.asax.cs and change the calls to MapRoute into MapAsyncRoute, like this:

[code:c#]

routes.MapAsyncRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = "" }
);

[/code]

No need to worry: all existing synchronous controllers in your project will keep on working. The MapAsyncRoute automatically falls back to MapRoute when needed.

Next, fire up a search-and-replace on your Web.config file, replacing

[code:c#]

<add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

[/code]

with:

[code:c#]

<add verb="*" path="*.mvc" validate="false" type="Microsoft.Web.Mvc.MvcHttpAsyncHandler, Microsoft.Web.Mvc"/>

<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="Microsoft.Web.Mvc.MvcHttpAsyncHandler, Microsoft.Web.Mvc"/>

[/code]

If you now inherit all your controllers from AsyncController instead of Controller, you are officially ready to begin some asynchronous ASP.NET MVC development.

Asynchronous patterns

Ok, that was a lie. We are not ready yet to start asynchronous ASP.NET MVC development. There’s a choice to make: which asynchronous pattern are we going to implement?

The AsyncController offers 3 distinct patterns to implement asynchronous action methods.. These patterns can be mixed within a single AsyncController, so you actually pick the one that is appropriate for your situation. I’m going trough all patterns in this blog post, starting with…

The IAsyncResult pattern

The IAsyncResult pattern is a well-known pattern in the .NET Framework and is well documened on MSDN. To implement this pattern, you should create two methods in your controller:

[code:c#]

public IAsyncResult BeginIndexIAsync(AsyncCallback callback, object state) { }
public ActionResult EndIndexIAsync(IAsyncResult asyncResult) { }

[/code]

Let’s implement these methods. In the BeginIndexIAsync method, we start reading a file on a separate thread:

[code:c#]

public IAsyncResult BeginIndexIAsync(AsyncCallback callback, object state) {
    // Do some lengthy I/O processing... Can be a DB call, file I/O, custom, ...
    FileStream fs = new FileStream(@"C:\Windows\Installing.bmp",
        FileMode.Open, FileAccess.Read, FileShare.None, 1, true);
    byte[] data = new byte[1024]; // buffer

    return fs.BeginRead(data, 0, data.Length, callback, fs);
}

[/code]

Now, in the EndIndexIAsync action method, we can fetch the results of that operation and return a view based on that:

[code:c#]

public ActionResult EndIndexIAsync(IAsyncResult asyncResult)
{
    // Fetch the result of the lengthy operation
    FileStream fs = asyncResult.AsyncState as FileStream;
    int bytesRead = fs.EndRead(asyncResult);
    fs.Close();

    // ... do something with the file contents ...

    // Return the view
    ViewData["Message"] = "Welcome to ASP.NET MVC!";
    ViewData["MethodDescription"] = "This page has been rendered using an asynchronous action method (IAsyncResult pattern).";
    return View("Index");
}

[/code]

Note that standard model binding takes place for the normal parameters of the BeginIndexIAsync method. Only filter attributes placed on the BeginIndexIAsync method are honored: placing these on the EndIndexIAsync method is of no use.

The event pattern

The event pattern is a different beast. It also consists of two methods that should be added to your controller:

[code:c#]

public void IndexEvent() { }
public ActionResult IndexEventCompleted() { }

[/code]

Let’s implement these and have a look at the details. Here’s the IndexEvent method:

[code:c#]

public void IndexEvent() {
    // Eventually pass parameters to the IndexEventCompleted action method
    // ... AsyncManager.Parameters["contact"] = new Contact();

    // Add an asynchronous operation
    AsyncManager.OutstandingOperations.Increment();
    ThreadPool.QueueUserWorkItem(o =>
    {
        Thread.Sleep(2000);
        AsyncManager.OutstandingOperations.Decrement();
    }, null);
}

[/code]

We’ve just seen how you can pass a parameter to the IndexEventCompleted action method. The next thing to do is tell the AsyncManager how many outstanding operations there are. If this count becomes zero, the IndexEventCompleted  action method is called.

Next, we can consume the results just like we could do in a regular, synchronous controller:

[code:c#]

public ActionResult IndexEventCompleted() {
    // Return the view
    ViewData["Message"] = "Welcome to ASP.NET MVC!";
    ViewData["MethodDescription"] = "This page has been rendered using an asynchronous action method (Event pattern).";
    return View("Index");
}

[/code]

I really think this is the easiest-to-implement asynchronous pattern available in the AsyncController.

The delegate pattern

The delegate pattern is the only pattern that requires only one method in the controller. It basically is a simplified version of the IAsyncResult pattern. Here’s a sample action method, no further comments:

[code:c#]

public ActionResult IndexDelegate()
{
    // Perform asynchronous stuff
    AsyncManager.RegisterTask(
        callback => ...,
        asyncResult =>
        {
            ...
        });

    // Return the view
    ViewData["Message"] = "Welcome to ASP.NET MVC!";
    ViewData["MethodDescription"] = "This page has been rendered using an asynchronous action method (Delegate pattern).";
    return View("Index");
}

[/code]

Conclusion

First of all, you can download the sample code I have used here: MvcAsyncControllersExample.zip (64.24 kb)

Next, I think this is really a feature that should be included in the next ASP.NET MVC release. It can really increase the number of simultaneous requests that can be processed by your web application if it requires some longer running I/O code that otherwise would block the ASP.NET worker thread. Development is a little bit more complex due to the nature of multithreading: you’ll have to do locking where needed, work with IAsyncResult or delegates, …

In my opinion, the “event pattern” is the way to go for the ASP.NET MVC team, because it is the most readable. Also, there’s no need to implement IAsyncResult classes for your own long-running methods.

Hope this clarified AsyncControllers a bit. Till next time!

kick it on DotNetKicks.com

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

8 responses

  1. Avatar for Steve
    Steve April 8th, 2009

    Thanks for the follow up post on this subject - much appreciated!

  2. Avatar for yazılım
    yazılım April 12th, 2009

    I never heard about AsyncController until your last post about futures assembly. Been waiting for some detailed description. Thanks for sharing

  3. Avatar for BB
    BB April 16th, 2009

    I'm still waiting for a detailed description, because this isn't it.

    This is a start, but this is nowhere near enough information to help one actually implement this.

    The event pattern looks like a real winner, as it seems like you can avoid rewriting your long running code to use beginFoo and endFoo, but I can't tell because the writeup is so short and the example so trivial.

    Even the suggestion that I can pass parameters to indexCompleted isn't real helpful. The example creates the parameter before the long running operation starts. In 99% of cases, what I want to pass to my indexCompleted method is the [b]result [/b]of some long running operation.

  4. Avatar for maartenba
    maartenba April 16th, 2009

    Well, the first method is the one that is executed before starting the new thread. In here, you can fetch some parameters, for example:

    public void Details(int productId) { }

    This method can then start a new thread:

    public void Details(int productId) {
    // If you need productId in the method that renders the view, pass it on in here:
    // AsyncManager.Parameters["productId"] = productId;

    // Add an asynchronous operation
    AsyncManager.OutstandingOperations.Increment();
    ThreadPool.QueueUserWorkItem(o =>
    {
    // ... fetch product details from database, based on productId ...
    // ... pass the fetched product to the method that renders the view ...
    AsyncManager.Parameters["product"] = productFromDatabase;

    AsyncManager.OutstandingOperations.Decrement();
    }, null);
    }

    Next, when the thread has finished, this one is called:

    public ActionResult DetailsCompleted() {
    // The product we fetched on another thread:
    ViewData["Product"] = AsyncManager.Parameters["product"];

    // Return the view
    return View("Details");
    }

    Hope this answers your question, if not, do not hesitate to ask.

  5. Avatar for Stuart Cam
    Stuart Cam May 8th, 2009

    Full example using NServiceBus here:

    http://blog.codebrain.co.uk...

  6. Avatar for maartenba
    maartenba May 8th, 2009

    Thanks for sharing! (good post BTW)

  7. Avatar for Shane Sievers
    Shane Sievers September 29th, 2009

    @maartenba, Thanks for this article. It does seem a bit light on explanation but I appreciate it just the same. In my project, I'm working on a situation where a user uploads a document of search queries. For each search query I will be compiling thousands or hundreds of thousands of records and then saving them to a file. I have all my methods written for doing this, but I'm a bit unclear as to how I can wire this up to your examples.

    For Instance, my initial form is on the Index view. When a user adds a file and clicks 'Search', the form posts back to my Index (POST) action to be evaluated. If the form contained a file, I need to launch my Async code - how do you call that from here? If you would, please consider the following:

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Index()
    {
    var viewModel = new MySearchViewModel();
    return View(viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Index(MySearchViewModel viewModel)
    {
    if (!ModelState.IsValid)
    return View(viewModel);

    #region File Upload
    if (viewModel.SearchByFile != null && viewModel.SearchByFile.ContentLength > 0) // Process File upload searches
    {
    AsyncManager.OutstandingOperations.Increment();
    ThreadPool.QueueUserWorkItem(o =>
    {
    ProcessFileUpload(); // ... --- THIS is the code that I want to run asynchronously ---//
    AsyncManager.OutstandingOperations.Decrement();
    }, null);
    }
    }

    public ActionResult IndexCompleted()
    {
    // Return the view
    var viewModel = new MySearchViewModel();
    viewModel.ProcessingMessage = "Just finished working on your file!";
    return View(viewModel);
    }

    Now when I visit my Index view, the ProcessingMessage equals "Just finished working on your file". Thats odd, why did it hit the IndexCompleted already? Also, my code in the ProcessFileUpload method fails because I believe it no longer has the repository in session. Any ideas on if this pattern will work for what I'm trying to do or if I should implement a different solution?

    Thanks in advance.

  8. Avatar for maartenba
    maartenba September 30th, 2009

    That is indeed odd. Did you try any of the other async methods?