Using an Asynchronous Controller in ASP.NET MVC

Visual Studio 2010

The AsyncController class enables you to write asynchronous action methods. You can use asynchronous action methods for long-running, non-CPU bound requests. This avoids blocking the Web server from performing work while the request is being processed. A typical use for the AsyncController class is long-running Web service calls.

This topic contains the following sections:

A Visual Studio project with source code is available to accompany this topic: Download.

On the Web server, the .NET Framework maintains a pool of threads that are used to service ASP.NET requests. When a request arrives, a thread from the pool is dispatched to process that request. If the request is processed synchronously, the thread that processes the request is blocked while the request is being processed, and that thread cannot service another request.

This might not be a problem, because the thread pool can be made large enough to accommodate many blocked threads. However, the number of threads in the thread pool is limited. In large applications that process multiple simultaneous long-running requests, all available threads might be blocked. This condition is known as thread starvation. When this condition is reached, the Web server queues requests. If the request queue becomes full, the Web server rejects requests with an HTTP 503 status (Server Too Busy).

In applications where thread starvation might occur, you can configure actions to be processed asynchronously. An asynchronous request takes the same amount of time to process as a synchronous request. For example, if a request makes a network call that requires two seconds to complete, the request takes two seconds whether it is performed synchronously or asynchronously. However, during an asynchronous call, the server is not blocked from responding to other requests while it waits for the first request to complete. Therefore, asynchronous requests prevent request queuing when there are many requests that invoke long-running operations.

When an asynchronous action is invoked, the following steps occur:

  1. The Web server gets a thread from the thread pool (the worker thread) and schedules it to handle an incoming request. This worker thread initiates an asynchronous operation.

  2. The worker thread is returned to the thread pool to service another Web request.

  3. When the asynchronous operation is complete, it notifies ASP.NET.

  4. The Web server gets a worker thread from the thread pool (which might be a different thread from the thread that started the asynchronous operation) to process the remainder of the request, including rendering the response.

The following illustration shows the asynchronous pattern.

Async pipeline

This section lists guidelines for when to use synchronous or asynchronous action methods. These are just guidelines; you must examine each application individually to determine whether asynchronous action methods help with performance.

In general, use synchronous pipelines when the following conditions are true:

  • The operations are simple or short-running.

  • Simplicity is more important than efficiency.

  • The operations are primarily CPU operations instead of operations that involve extensive disk or network overhead. Using asynchronous action methods on CPU-bound operations provides no benefits and results in more overhead.

In general, use asynchronous pipelines when the following conditions are true:

  • The operations are network-bound or I/O-bound instead of CPU-bound.

  • Testing shows that the blocking operations are a bottleneck in site performance and that IIS can service more requests by using asynchronous action methods for these blocking calls.

  • Parallelism is more important than simplicity of code.

  • You want to provide a mechanism that lets users cancel a long-running request.

The downloadable sample shows how to use asynchronous action methods effectively. The sample program calls the Sleep method to simulate a long-running process. Few production applications will show such obvious benefits to using asynchronous action methods.

You should test applications to determine whether asynchronous methods provide a performance benefit. In some cases it might be better to increase the IIS maximum concurrent requests per CPU and the maximum concurrent threads per CPU. For more information about ASP.NET thread configuration, see the entry ASP.NET Thread Usage on IIS 7.0 and 6.0 on Thomas Marquardt's blog. For more information about when to make asynchronous database calls, see the entry Should my database calls be Asynchronous? on Rick Anderson's blog.

Few applications require all action methods to be asynchronous. Often, converting a few synchronous action methods to asynchronous methods provides the best efficiency increase for the amount of work required.

The sample code below shows a synchronous action method that is used to display news items from a portal controller. The request Portal/News?city=Seattle displays news for Seattle.

The following example shows the News action method rewritten as an asynchronous method.

Public Class PortalController
    Inherits AsyncController
    Public Sub NewsAsync(ByVal city As String) 
        Dim newsService As New NewsService() 
        newsService.GetHeadlinesCompleted += Function(sender, e) Do 
        AsyncManager.Parameters("headlines") = e.Value 
    End Function 
End Sub
    Public Function NewsCompleted(ByVal headlines() As String) _
            As ActionResult
        Return View("News", New ViewStringModel() {NewsHeadlines=headlines})
    End Function
End Class

To convert a synchronous action method to an asynchronous action method involves the following steps:

  1. Instead of deriving the controller from Controller, derive it from AsyncController. Controllers that derive from AsyncController enable ASP.NET to process asynchronous requests, and they can still service synchronous action methods.

  2. Create two methods for the action. The method that initiates the asynchronous process must have a name that consists of the action and the suffix "Async". The method that is invoked when the asynchronous process finishes (the callback method) must have a name that consists of the action and the suffix "Completed". In the previous example, the News method has been turned into two methods: NewsAsync and NewsCompleted.

    The NewsAsync method returns void (no value in Visual Basic). The NewsCompleted method returns an ActionResult instance. Although the action consists of two methods, it is accessed using the same URL as for a synchronous action method (for example, Portal/News?city=Seattle). Methods such as RedirectToAction and RenderAction will also refer to the action method as News and not NewsAsync.

    The parameters that are passed to NewsAsync use the normal parameter binding mechanisms. The parameters that are passed to NewsCompleted use the Parameters dictionary.

  3. Replace the synchronous call in the original ActionResult method with an asynchronous call in the asynchronous action method. In the example above, the call to newsService.GetHeadlines is replaced with a call to newsService.GetHeadlinesAsync.

The NewsService class that is consumed by the NewsAsync method is an example of a service that exposes methods using an event-based asynchronous pattern. For more information about this pattern, see Event-based Asynchronous Pattern Overview.

The OutstandingOperations property notifies ASP.NET about how many operations are pending. This is required because ASP.NET cannot determine how many operations were initiated by the action method or when those operations are complete. When OutstandingOperations property is zero, ASP.NET completes the overall asynchronous operation by calling the NewsCompleted method.

Note the following about asynchronous action methods:

  • If the action name is Sample, the framework will look for SampleAsync and SampleCompleted methods.

  • View pages should be named Sample.aspx rather than SampleAsync.aspx or SampleCompleted.aspx. (The action name is Sample, not SampleAsync.)

  • A controller cannot contain an asynchronous method named SampleAsync and a synchronous method named Sample. If it does, an AmbiguousMatchException exception is thrown because the SampleAsync action method and the Sample action method have the same request signature.

Asynchronous action methods are useful when an action must perform several independent operations. For example, a portal site might show not only news, but sports, weather, stocks, and other information.

The following example shows a synchronous version of news portal Index action method.

Public Function IndexSynchronous(ByVal city As String) As ActionResult
    Dim newsService As NewsService = New NewsService
    Dim headlines() As String = newsService.GetHeadlines
    Dim sportsService As SportsService = New SportsService
    Dim scores() As String = sportsService.GetScores
    Dim weatherService As WeatherService = New WeatherService
    Dim forecast() As String = weatherService.GetForecast
    Return View("Common", New PortalViewModel() {NewsHeadlines=headlines, SportsScores=scores, Weather=forecast})
End Function

The calls to each service are made sequentially. Therefore, the time that is required in order to respond to the request is the sum of each service call plus a small amount of overhead. For example, if the calls take 400, 500, and 600 milliseconds, the total response time will be slightly more than 1.5 seconds. However, if the service calls are made asynchronously (in parallel), the total response time will be slightly more than 600 milliseconds, because that is the duration of the longest task.

The following example shows an asynchronous version of the news portal Index action method.

Public Sub IndexAsync(ByVal city As String)
    Dim newsService As New NewsService() 
    newsService.GetHeadlinesCompleted += Function(sender, e) Do 
        AsyncManager.Parameters("headlines") = e.Value 
    End Function 
    Dim sportsService As New SportsService() 
    sportsService.GetScoresCompleted += Function(sender, e) Do 
        AsyncManager.Parameters("scores") = e.Value 
    End Function 
    Dim weatherService As New WeatherService() 
    weatherService.GetForecastCompleted += Function(sender, e) Do 
        AsyncManager.Parameters("forecast") = e.Value 
    End Function 
End Sub

Public Function IndexCompleted(ByVal headlines() As String, _
        ByVal scores() As String, _
        ByVal forecast() As String) As ActionResult
    Return View("Common", New PortalViewModel() {NewsHeadlines=headlines, SportsScores=scores, Weather=forecast})
End Function

In the previous example, the Increment method is called with a parameter of 3 because there are three asynchronous operations.

If you want to apply attributes to an asynchronous action method, apply them to the ActionAsync method instead of to the ActionCompleted method. Attributes on the ActionCompleted method are ignored.

Two new attributes have been added: AsyncTimeoutAttribute and NoAsyncTimeoutAttribute. These attributes let you control the asynchronous timeout period.

If an asynchronous action method calls a service that exposes methods by using the BeginMethod/EndMethod pattern, the callback method (that is, the method that is passed as the asynchronous callback parameter to the Begin method) might execute on a thread that is not under the control of ASP.NET. In that case, HttpContext.Current will be null, and the application might experience race conditions when it accesses members of the AsyncManager class such as Parameters. To make sure that you have access to the HttpContext.Current instance and to avoid the race condition, you can restore HttpContext.Current by calling Sync from the callback method.

If the callback completes synchronously, the callback will be executed on a thread that is under the control of ASP.NET and the operations will be serialized so there are no concurrency issues. Calling Sync from a thread that is already under the control of ASP.NET has undefined behavior.

The ActionCompleted method will always be called on a thread that is under the control of ASP.NET. Therefore, do not call fSync from that method.

The callback that you pass to the Begin method might be called using a thread that is under the control of ASP.NET. Therefore, you must check for this condition before you call Sync. If the operation completed synchronously (that is, if CompletedSynchronously is true), the callback is executing on the original thread and you do not have to call Sync. If the operation completed asynchronously (that is, CompletedSynchronously is false), the callback is executing on a thread pool or I/O completion port thread and you must call Sync.

For more information about the BeginMethod/EndMethod pattern, see Asynchronous Programming Overview and the entry Using the BeginMethod/EndMethod pattern with MVC on Rick Anderson's blog.

The following table lists the key classes for asynchronous action methods.




Provides the base class for asynchronous controllers.


Provides asynchronous operations for the AsyncController class.

Community Additions