Improving Scalability and Performance with Asynchronous Controllers

The Task-Based Asynchronous Pattern provides a simple and easy way to improve the performance and scalability of MVC 4 web applications.  Every web application has a finite number of threads available to handle requests, each of which has a certain degree of memory overhead.  Asynchronous methods take the same amount of time to run as synchronous methods, however asynchronous release their executing thread, allowing more simultaneous requests to be processed.

Asynchronous methods are not appropriate for every situation.  Synchronous methods are more appropriate for short simple operations, and operations that are CPU intensive.  Asynchronous methods are appropriate however in situations that parallel processing is possible, situations that rely on network requests, or any long running requests that are holding up the site performance.  Additionally, any process that you want to be able to cancel before completion should be asynchronous.  Figure 1 shows the results of a test where the IIS thread limit was set to 50 and the number of concurrent requests increased over time.  This figure makes clear the advantage of asynchronous methods over of synchronous ones in situations with a high volume of concurrent requests.


Figure 1 Synchronous vs. asynchronous response times by number of concurrent requests

With the inclusion of the Task library as well as the await and async keywords, asynchronous methods have become much simpler in MVC 4, though asynchronous controllers are possible as far back as MVC 2.  Figure 2 shows side-by-side synchronous and asynchronous method calls to achieve the same end result.  The key changes to make a method asynchronous are

  • Changing the return type to Task<ActionResult>
  • Using the async keyword to mark the method
  • Using the await keyword on the GizmoService call

(Appending “Async” to the method name is not required, but considered good practice)

Controller Methods

Synchronous

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos",  gizmoService.GetGizmos());
}

Asynchronous

public async Task<ActionResult> GizmosAsync()
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", await gizmoService.GetGizmosAsync());
}

GizmoService Methods

Synchronous

public List<Gizmo> GetGizmos()
{
    var uri = Util.getServiceUri("Gizmos");
    using (WebClient webClient = new WebClient())
    {
    return JsonConvert.DeserializeObject<List<Gizmo>>(webClient.DownloadString(uri));
}
}

Asynchronous

public async Task<List<Gizmo>> GetGizmosAsync()
{
    var uri = Util.getServiceUri("Gizmos");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<List<Gizmo>>;());
    }
}

Figure 2 Synchronous vs. asynchronous method examples

Task<TResult> is a class in the System.Threading.Tasks namespace, and provides a hook to notify any listener that work has been completed.  The async keyword indicates to the compiler that the function is asynchronous, and is meant to be paired with a corresponding await keyword.  Without using await, the method will be executed synchronously.  await releases the controlling thread back into the thread pool, and that execution will continue when the corresponding task finishes.  In this case, GizmosAsync() will run on an IIS thread, same as Gizmos() until it executes “await  gizmoService.GetGizmosAsync()”, then the IIS thread will be released back to the thread pool until GetGizmosAsync indicates that it is ready, at which point an IIS thread will pick up the execution and finish the method.

Use of Asynchronous methods also allows the performance enhancements offered by parallel processing.  Figure 3 shows a synchronous and asynchronous method for getting three lists of objects.  The synchronous method must execute each request sequentially before returning a value; the asynchronous method requests all three requests simultaneously and so can return a value as soon as the last Task completes.

Controller Methods

Synchronous

public ActionResult PWG()
{
    ViewBag.SyncType = "Synchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var pwgVM = new ProdGizWidgetVM(
        widgetService.GetWidgets(),
        prodService.GetProducts(),
        gizmoService.GetGizmos());
    return View("PWG", pwgVM);
}

Asynchronous

public async Task PWGAsync()
{
    ViewBag.SyncType = "Asynchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();
    var widgetTask = widgetService.GetWidgetsAsync();
    var prodTask = prodService.GetProductsAsync();
    var gizmoTask = gizmoService.GetGizmosAsync();

    await Task.WhenAll(widgetTask, prodTask, gizmoTask);

    var pwgVM = new ProdGizWidgetVM(
        widgetTask.Result,
        prodTask.Result,
        gizmoTask.Result);    
    return View("PWG", pwgVM);
}

Figure 3 Synchronous vs. asynchronous method executing multiple tasks

A final feature of asynchronous methods is their ability to be cancelled before completion.  This allows users to cancel long-running tasks without having to take an action such as leaving or refreshing the page.  Additionally, it provides for the automatic time-out of potentially long-running functions, throwing the designated error.

[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException), View = "TimeoutError")]
public async Task GizmosCancelAsync(CancellationToken cancellationToken )
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", await gizmoService.GetGizmosAsync(cancellationToken));
}

Figure 4 Example of a cancellation token in an asynchronous method

As these examples show, asynchronous Controllers in MVC provide for improved scalability and performance over the use of just synchronous methods.  Asynchronous Controllers and methods can and should be utilized in any MVC web application that needs faster performance during long running processes or will handle high-volume requests.

References

  1. http://msdn.microsoft.com/en-us/vs11trainingcourse_aspnetmvc4_topic5.aspx
  2. http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4
  3. http://dotnet.dzone.com/news/net-zone-evolution
  4. http://blog.stevensanderson.com/2010/01/25/measuring-the-performance-of-asynchronous-controllers/

Leave a Reply

Your email address will not be published. Required fields are marked *