Async Await and ASP.NET MVC

.NET 4.5 introduced the Async and Await keywords to make Task based asynchronous programming more linear and easier to understand. Today we take a quick look at doing async actions in ASP.NET MVC and how these can potentially end up improving the server side performance of your MVC Application.

Benefits of Asynchronous Model

As we know, ASP.NET has a max number of threads that it can pick up from its thread pool. When all the threads are busy, a new request is queued and has to wait till a thread is available to service the request. Thus in ASP.NET, one surefire way to gain better scale is to ensure that your requests don’t wait on threads unnecessarily. If you are thinking sure enough I’ll fire off a background thread and be done with it, you are wrong, don’t do it. Background thread is still a ‘thread’ and you are not gaining anything; uncontrolled background thread creation will only exhaust your thread pool faster. Key to ASP.NET scaling is letting go of a thread as-soon-as-possible.

Async Controllers or Async Action Methods

Well before .NET 4.5 (and MVC 4), doing Async in controllers required some plumbing to work with the Task Based Async pattern. However with the introduction of the async/await keywords, this plumbing has been pushed down to the language level. As a result, you don’t need your controller to inherit from AsyncController anymore. The AsyncController class remains to support backward compatibility with ASP.NET MVC 3.

To do Async in ASP.NET MVC, all you have to is return a Task and use the async keyword in your action methods. Enough of theory, let’s walk the talk.

Async in an ASP.NET MVC Controller

We will build a sample application where the controller aggregates information by making calls to multiple Web API calls and then sends it back to the View.

We will profile this activity using the super useful MiniProfiler library by Stack Exchange and see how Async helps us

The Sample Application

Step 1: Create an empty ASP.NET MVC4 project using .NET Framework 4.5 and the Internet project template.

Step 2: Add a class in the Models folder call Blog with the following properties in it

public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string Post { get; set; }
public string Author { get; set; }
}


Step 3: Add an API Controller with a Method GetPosts and add the following code to it

[HttpGet]
public IList<Blog> GetPosts()
{
List<Blog> blogs = new List<Blog>();
for (int i = 0; i < 10; i++)
{
  blogs.Add(new Blog
  {
   Id = i,
   Title = "Title " + i.ToString(),
   Post = "Post " + i.ToString(),
   Author = "Author" + i.ToString()
  });
}
Thread.Sleep(1000);
return blogs;
}


This controller method simply loops 10 time and creates 10 dummy objects and then waits for a second.

Step 4: Add another API Controller with a Method GetFavorites and add the same code as above to it. We are just creating two ‘dummy’ service methods to call from your Action Method.

Step 5: In the HomeController’s Index method, add the following code. It creates a WebClient instance and calls the Web Api to download a list of Blogs. It blocks till the blogs are returned. Then it calls the Favorties api to get the favorites. Web API call returns JSON by default.

We convert the JSON into list of objects and push it in the ViewBag.

public ActionResult Index()
{
WebClient client = new WebClient();
var blogs = client.DownloadString(new Uri("
http://localhost:49728/api/Blogs"));
var favorites = client.DownloadString(new Uri("http://localhost:49728/api/Favorites"));
ViewBag.Blogs = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Blog>>(blogs);
ViewBag.Favorites = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Blog>>(favorites);
return View();
}


Step 6: Update the Index.cshtml with the following markup

@{
    ViewBag.Title = "Home Page";
}
<h3>We suggest the following:</h3>
<ul class="round">
@foreach (var item in ViewBag.Blogs)
{
  <li>
   <h1>@item.Title</h1>
   <h3>by @item.Author</h3>
   <p>@item.Post</p>
  </li>
  }
</ul>


Step 7: Install MiniProfiler from Nuget

PM> install-package MiniProfiler

Step 8: Setup the MiniProfiler by initializing it in the Global.asax

protected void Application_BeginRequest()
{
    if (Request.IsLocal)
    {
        MiniProfiler.Start();
    }
}


protected void Application_EndRequest()
{
    MiniProfiler.Stop();
}


Add the rendering script command at the bottom of _Layout.cshtml

<html>
  …
  @Scripts.Render("~/bundles/jquery")
  @RenderSection("scripts", required: false)
  @MiniProfiler.RenderIncludes()
</body>
</html>


Add the following line <system.webserver><handlers> section

<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />

Step 9: All set, let’s run the application.

non-async-call-timing

Going Async

Now that we have got the time taken by the non-async technique, let go async. Replace the Index method with the following:

public async Task<ActionResult> Index()
{
WebClient client = new WebClient();
var blogs = client.DownloadStringTaskAsync(new Uri("
http://localhost:49728/api/Blogs"));
WebClient client2 = new WebClient();
var favorites = client2.DownloadStringTaskAsync(new Uri("
http://localhost:49728/api/Favorites"));
await Task.WhenAll(blogs, favorites);
ViewBag.Blogs = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Blog>>(blogs.Result);
ViewBag.Favorites = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Blog>>(favorites.Result);
return View();
}


The above code calls the DownloadStringTaskAsync method that returns a Task. The code executes linearly till it reaches Task.WhenAll. This statement tells the runtime that it needs to wait till both the tasks are complete. So at this point, the thread is released till both the Tasks complete at which point, rest of the statements are executed and View is returned.

The difference between the DownloadStringTaskAsync and DownloadString used earlier is that the Task returning method is NON blocking. It doesn’t block till all the data is returned.

Okay, our theory sounds good. How does it look practically? Let’s run the application.

async-call-timing

Wow! Almost half the time? How? Well, as I mentioned earlier, both the DownloadStringTaskAsyn methods are non-blocking and as a result they are called immediately and the 1 seconds waits happen in parallel instead of sequentially as in case of the DownloadString method.

Conclusion

We saw how we can use the async/await syntax in MVC Controller’s action methods in ASP.NET MVC. We also saw the potential gains of async execution.

Download the entire source code of this article (Github)




About The Author

Suprotim Agarwal
Suprotim Agarwal, Developer Technologies MVP (Microsoft Most Valuable Professional) is the founder and contributor for DevCurry, DotNetCurry and SQLServerCurry. He is the Chief Editor of a Developer Magazine called DNC Magazine. He has also authored two Books - 51 Recipes using jQuery with ASP.NET Controls. and The Absolutely Awesome jQuery CookBook.

Follow him on twitter @suprotimagarwal.

3 comments:

Anonymous said...

Your async method didn't improve performance. The fact that you threaded the calls improved performance (via Task.WhenAll()). Your async method simply free'd up a thread to serve up more requests.

Sorry, this is a very misguided article.

Anonymous said...

@Anonymous whaa??? Did you read what you wrote yourself?

"Your async method simply free'd up a thread to serve up more requests."

How does serving up more requests NOT mean better performance???

Beryl Small said...

My problem is that my project is ALL async and when I try to render a partial view where the data is fetched async it gives me an error.