How to Use Asynchronous Controllers in ASP.NET MVC2 & MVC3
The primary reason I added asynchronous methods to Quick and Dirty Feed Parser 0.3 was because I wanted to use QD Feed Parser in conjunction with asynchronous controllers in ASP.NET MVC3.
MSDN has some excellent documentation which explains the ins and outs of asynchronous controllers in ASP.NET MVC, yet there aren’t many good examples of how to use it online. Given this, I thought I would make one.
Asynchronous Controllers in Theory
Here’s how asynchronous controllers work:
And in case my visual isn’t clear:
- A user sends a request to some resource on your server, i.e. GET: /controller/action
- Since the action method is part of an asynchronous controller, the processing of the request is handed off to a CLR thread pool worker while the IIS thread bails and serves other requests. This is the key advantage of asynchronous controllers: your limited, precious IIS threads get to off-load long-running, blocking tasks to inexpensive CLR worker threads, freeing up said IIS threads to serve more requests while work is being done in the background.
- The CLR worker thread diligently works away while the IIS worker thread gets recycled.
- The CLR worker thread finishes its task, and invokes the AsnycManager.Sync method and passes back some piece of processed data to be returned to the end-user.
- The AsyncManager hitches a ride on the first available IIS thread and passes along the processed data from the CLR worker thread into a special action method which returns some sort of consumable data back to the User, such as a View or a JsonResult.
Asynchronous controllers are all about utilizing additional, cheap CLR threads to free up expensive IIS threads as often as possible. In any situation where you have a long-running IO-bound task, they’re a good way to improve the performance of your ASP.NET MVC application.
Asynchronous Controllers in Code
To create an asynchronous controller, all you have to do is inherit from the AsyncController class:
public class FeedController : AsyncController {}
For every asynchronous action method you want to add to your controller, you have to actually supply two different methods:
// GET: /Feed/ public void FeedAsync(string feeduri, int itemCount){ ... } public JsonResult FeedCompleted(IFeed feed, int itemCount){ ... }
Both of these methods on the asynchronous controller support the same action method (“Feed”) and any request that goes to “Feed/Feed” in our ASP.NET MVC application will be served asynchronously.
Asynchronous Action Naming Conventions
Both methods have to follow these naming conventions:
- The first method is the worker method that actually performs the long-running task on a worker thread; it should return void; and it must be named [Action Name]Async.
- The second method is the output method which is joined back to an IIS worker thread by the AsyncManager; it must return a valid ActionResult derivative (View, RouteResult, JsonResult, etc…;) and it must be named [Action Name]Completed.
Full Asynchronous Action Methods
Here’s full source code from the asynchronous controller I used to build Geeky Reads, minus a bit of work I put in to use object caching:
public class FeedController : AsyncController { protected IFeedFactory _feedfactory; public FeedController(IFeedFactory factory){ _feedfactory = factory; } // GET: /Feed/ public void FeedAsync(string feeduri, int itemCount){ AsyncManager.OutstandingOperations.Increment(); _feedfactory.BeginCreateFeed(new Uri(feeduri), async => AsyncManager.Sync( () => { var feed = _feedfactory.EndCreateFeed(async); AsyncManager.Parameters["feed"] = feed; AsyncManager.Parameters["itemCount"] = itemCount; AsyncManager.OutstandingOperations.Decrement(); })); } public JsonResult FeedCompleted(IFeed feed, int itemCount){ return Json(FeedSummarizer.SummarizeFeed(feed, itemCount), JsonRequestBehavior.AllowGet); } }
Here’s what you should be looking at in this example:
AsyncManager.OutstandingOperations.Increment and .Decrement – the number of .Decrement operations must mach the number of .Increment operations; otherwise, the AsyncManager will time out operations that are running too long and the completion method will never return an ActionResult to the end user.
The accounting is pretty simple: you call .Increment at the beginning of your long-running operation, and .Decrement once your operation is finished and the results have been passed into the AsyncManager.Parameters collection, which brings me to my next point.
AsycManager Parameters and Argument Names for the Completion Method
Notice how the AsyncManager parameters “feed” and “itemCount” match the argument names for the FeedCompleted method – that’s not an accident. The AsyncManager binds its parameter collection to the FeedCompleted method arguments once the .Sync method completes, and this is what allows the asynchronous method results to be passed back to the consumer.
If you’d like to see the full source for this example, check it out on Github.