Creating Asynchronous Actions in ASP.NET MVC

Introduction

Asynchronous actions allow you to handle more concurrent requests and can be implemented using async / await keywords. Asynchronous actions are useful in situations where you are performing some network operation such as calling a remote service. This article discusses asynchronous actions and also shows how to create them in an ASP.NET MVC.

Overview of Asynchronous Actions

Before you create an asynchronous action, let’s quickly understand what asynchronous processing is with respect to ASP.NET MVC and how it is beneficial to your application.

Suppose that you are developing an ASP.NET MVC application that heavily relies on some third-party service. Consider the following action method as an example:

public ActionResult Index()
{
    DbHelper helper = new DbHelper();
    List<Customer> data = helper.GetCustomerData();
    return View(data);
}

The above code shows an Index() action method that returns an ActionResult. Inside, it creates an instance of a helper class (DbHelper) and calls its GetCustomerData() method. The GetCustomerData() method wraps the remote service call and returns the data returned by the service as a List of Customer objects. The data is then passed to the Index view as its model.

The Index() action method shown above executes in asynchronous manner. When a request lands to the Index() action, ASP.NET picks up a thread from its thread pool and runs the Index() method on the allotted thread. Since Index() action is synchronous, all the operations, including the remote service call, happen sequentially (one after the other). Once all the operations are over, the thread running the code can be reused for some other execution. Thus a thread from the thread pool is blocked for the entire duration of the execution – start to end. Let’s say this execution takes 10 seconds (just a hypothetical value).

Now assume that the Index() and GetCustomerData() method has been modified to work in asynchronous manner. When a request lands to the Index() action, ASP.NET picks up a thread from the thread pool as before. The Index() method starts running on a thread allotted to it. However, the allotted thread invokes the GetCustomerData() asynchronously and is immediately returned to the thread pool to serve other requests. When the remote service call returns the required data, another thread from the thread pool is allotted to finish the remainder of the Index() method code. Thus instead of blocking a thread for the entire duration of the processing, a thread is released as soon as the network operation starts and the processing resumes on some other thread when the network operation returns. Even in this case the total time taken for the processing is 10 seconds but the thread is freed to serve other requests. This results in improved handling of concurrent requests. Although there may not be any performance improvement as far as single requests processing time is concerned, the overall performance of the application may be better than before because there is less queuing of requests.

Synchronous operations are good when you wish to stick to a a simple programming model, operations are short running and CPU centric. On the other hand, asynchronous operations are good when concurrent request handling is more important than simplicity, operations are long running and network centric (such as remote Web API or service calls). 

Creating Asynchronous Actions

Now that you know the basics of asynchronous action methods, let’s create an asynchronous action method using async / await keywords of C#. To begin developing this sample application, create a new ASP.NET MVC application using empty project template. Then add an ADO.NET Entity Data Model for the Customers table of the Northwind database. The following figure shows this data model:

Customers table
Customers table

Then add a class to the project and name it DbHelper. This class contains the GetCustomerDataAsync() method as shown below:

public class DbHelper
{
    public async Task<List<Customer>> GetCustomerDataAsync()
    {
        NorthwindEntities db = new NorthwindEntities();
        var query = from c in db.Customers
                    orderby c.CustomerID ascending
                    select c;
        List<Customer> data = await query.ToListAsync();
        return data;
    }
}

This application doesn’t use any real network operation such as a service call. Just for the sake of testing, it fetches all the customers from the Customers table and returns to the caller.

The GetCustomerDataAsync() method returns a Task object that wraps a generic List of Customer entities. The GetCustomerData() is marked with the async keyword indicating that it is to be called in asynchronous manner. Since the method is asynchronous the method name ends with “Async”. Inside, a LINQ to Entities query is formed that fetches all the customers from the database. Notice that the data is realized by calling ToListAsync() method. The await keyword used in the ToListAsync() statement indicates that the execution should wait for ToListAsync() to complete. The List of Customer entities is then returned to the caller.

Now, add a controller to the Controllers folder and add the following code to it:

public async Task<ActionResult> IndexAsync()
{
    DbHelper helper = new DbHelper();
    List<Customer> data = await helper.GetCustomerDataAsync();
    return View(data);
}

This is the same Index() action you saw earlier but now it has been converted to its asynchronous version. The IndexAsync() method returns a Task object that wraps the ActionResult. It is also marked with an async keyword. Inside, it creates an instance of DbHelper class and calls the GetCustomerDataAsync() method. Notice the use of the await keyword. You will find that async and await always go hand in hand.

Now, add IndexAsync view to the project and add the following markup to it:

@model List<AsyncMVCDemo.Models.Customer>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <h1>List of Customers</h1>
    <table border="1" cellpadding="6">
        @foreach(var customer in Model)
        {
            <tr>
                <td>@customer.CustomerID</td>
                <td>@customer.CompanyName</td>
            </tr>
        }
    </table>
</body>
</html>

If you run the IndexAsync() action method you should get a list of customers displayed in a table (see below).

A list of customers
A list of customers

Dealing with Timeouts

While working with asynchronous operations you should take into account the possibility that a call is made that never returns. To tackle such situations ASP.NET MVC provides the [AsyncTimeout] attribute. The [AsyncTimeout] attribute specifies a timeout value in milliseconds for the asynchronous operation. The default timeout value is 45 seconds. Although we won’t get into more detail of the [AsyncTimeout] here, you can use it as follows:

[AsyncTimeout(2000)]
public async Task<ActionResult> IndexAsync()
{
  ...
}

The above code sets the timeout value to 2000 milliseconds. Just in case you don’t want to have any timeout or the asynchronous operation you can use the [NoAsyncTimeout] attribute.

[NoAsyncTimeout]
public async Task<ActionResult> IndexAsync()
{
  ...
}

Summary

ASP.NET MVC makes it easy for you to create asynchronous action methods by following the async / await pattern of .NET framework 4.5. Asynchronous operations allow action methods to cater to more concurrent requests than otherwise. Asynchronous action methods are useful for tasks involving network operations such as calling a remote service.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read