Web API, Async and Performance in an ASP.NET MVC application

Posted by: Suprotim Agarwal , on 10/10/2013, in Category ASP.NET
Views: 163248
Abstract: Async and performance are often a considered to go hand in hand because performance is bracketed with scaling. Today we explore the differences and see how to best leverage async operations to gain performance and build an ASP.NET MVC Web API Application that scales better.

With the multifold increase in processor performance as well as number of processor cores in a system, software systems are expected to scale horizontally with system hardware. However, software systems have the tough job of working with disparate systems that work at their own speeds (e.g. Network latency, Disk latencies, peripheral device latency and so on). So if we are building a system that does things synchronously, our throughput is always going to be limited by the slowest system in the chain.

Thus, the ability of our software to initiate a task and do something else till that task completes, goes a long way to ensure we don’t ‘bottleneck’ or ‘wait doing nothing’ on a slowly running component.

 

Rowan Miller had an excellent analogy of Async tasks to waiters at a restaurant, in his TechEd NA (2013) talk. To paraphrase – A system working asynchronously is like a waiter at a restaurant. More often than not, a waiter serving a table will be at Table 1, take an order, explain a menu item, deliver an order and then move away, free to do the same at Table 2. When Table 1 is done (deciding the order, requiring a refill, requesting the check) they will draw the waiter’s attention and the waiter would come back to Table 1 as soon as they become available (or immediately if they are available). This mechanism of one waiter serving multiple tables ensures that you don’t need as many waiters as the number of tables, to maintain optimal performance (in case of restaurant – experience).

If we consider a computation unit (CPU + Coprocessors + Cache + System Bus etc.) to be a waiter serving customers, then async operations is the way to make sure that they don’t waste time waiting while the customer decides what to order.

Enough analogies, let’s see some real code.

This article is published from the DotNetCurry .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months. Subscribe to this eMagazine for Free and get access to hundreds of free tutorials from experts

The Sample MVC Application

Let’s say we have an application whose home page is split into four parts showing Emails, Tasks, Bookmarks and Notes. Ideally these would probably come from different feeds from disparate systems, but to keep things simple, we’ll get them from four tables in a database. We’ve filled up the tables with some sample data that I generated.

This demo uses Visual Studio 2013 Preview, but you can use VS 2012 as well. For the performance testing pieces, you’ll need the Ultimate SKU, or you can use the free WCAT tool as well.

Since I want to focus on the performance bit, I will not do a step-by-step walkthrough of how to build the application. You can directly get the code and try it out. However this is how the application is laid out:

The Web API services App

The Web API services app is using the latest Web API binaries. If you are using Visual Studio 2012, you can create a Web API application and upgrade to Web API preview version using the –pre parameter in the Package Manager Console. If you are using Visual Studio 2013 Preview, you are already setup with the latest preview binaries for Web API 2.0.

Once the project is setup, we use the following command to install the latest version of Entity Framework (which is version 6 beta at the time of writing).

PM> install-package EntityFramework –pre

Next we add the four entities that will serve as our data source – Email, Task, Bookmark and Note, each with the following properties:

public class Email
{
    public int Id { get; set; }
    public string Mail { get; set; }
    public string From { get; set; }
    public string To { get; set; }
    public string Title { get; set; }
}

public class MyTask
{
    public int Id { get; set; }
    public string Starts { get; set; }
    public string Ends { get; set; }
    public string Title { get; set; }
    public string Details { get; set; }
}

public class Bookmark
{
    public int Id { get; set; }
    public string Url { get; set; }
    public string Title { get; set; }
}

public class Note
{
    public int Id { get; set; }
    public string Content { get; set; }
}

Finally we scaffold Entity Framework based Web API Controllers for each of the above entities, giving us the APIs required to do CRUD operations for these entities.

Our solution structure looks like the following

web-api-service-project-model

The Get APIs for each controller is as follows:

public IEnumerable GetBookmark()
{
return db.Bookmarks.AsEnumerable();
}

public IEnumerable GetEmail()
{
return db.Emails.AsEnumerable();
}

public IEnumerable GetNotes()
{
return db.Notes.AsEnumerable();
}

public IEnumerable GetTask()
{
return db.Tasks.AsEnumerable();
}

Instead of entering 100 rows of data manually, I used the GenerateData.com site to generate INSERT queries for the dummy data. They are included in the source code of the app.

The MVC Client App

In the same solution, we can add an ASP.NET MVC Client app that will use these web services. Now using Web Service in ASP.NET MVC app is trivial if you use AJAX to query service directly. But that would require CORS and other concerns like authentication etc. To keep things simple for us, we will instantiate a WebClient object and query each Web Service directly. The Client app will use its own proxy entities and return one Model object that we will bind in our MVC View.

Now this may not be the most elegant way to consume Web Services, but it will help us identify performance changes as we go from synchronous to asynchronous implementation.

Overall we end up with the following classes in our Client App

mvc-client-solution-structure

The DashboardController collates all the data from the WebAPI in the Index GET action method and returns an instance of a Dashboard Model to the Index view.

The Synchronous Application

The synchronous version of the Dashboard controller is as follows:

public ActionResult Index()
{
Stopwatch timer = Stopwatch.StartNew();
timer.Start();
var feeds = GetFeeds();
timer.Stop();
feeds.TimeTaken = timer.ElapsedMilliseconds;
return View(feeds);
}

public Dashboard GetFeeds()
{
string emails = new
  WebClient().DownloadString("http://localhost:18545/api/Emails");
string myTasks = new
  WebClient().DownloadString("http://localhost:18545/api/Tasks");
string notes = new
  WebClient().DownloadString("http://localhost:18545/api/Notes");
string bookmarks = new
  WebClient().DownloadString("http://localhost:18545/api/Bookmarks");
Dashboard dash = new Dashboard();
dash.Emails = Deserialize(emails);
dash.Bookmarks = Deserialize(bookmarks);
dash.Notes = Deserialize(notes);
dash.Tasks = Deserialize(myTasks);
return dash;
}

The Index.cshtml for the Dashboard is marked up such that it splits the screen into 4 parts each showing the Email, Bookmarks, Notes and Tasks. We also have a stopwatch that keeps track of how much time it takes to fetch the data on the page.

dashboard-home

Setting up a Performance Test Project

To positively quantify the change in performance characteristics, a simple timer like we have above is often misleading because going async almost never gives you an improved page load time on the client. However what it does give you is better page response on higher load. The only way to load test is use a load testing tool. Today we’ll use a Performance Test project in Visual Studio and setup a load of concurrent users. The key characteristics to watch out for are the Average Page Time and the Count of page hits while the test was running. Lets setup the project first.

Step 1: Add a new Project and select the Test project template “Web Performance and Load Test Project”.

add-new-performance-test-project

This will add a WebTest to the project automatically.

Step 2: Add a new Load Test to the project.

add-new-load-test

This will launch a new Load Test wizard.

new-load-test-wizard-step1

Click “Next” to setup the Scenario, you can keep the default here

new-load-test-wizard-step2

The “Next” page sets up the Load pattern. You can use the following settings but I had to tune it down to 10 maximum users, Start User count of 1 and Step User Count of 1. This completely depends on how far you can push your host system based on its hardware configuration. I hit the CPU and Memory thresholds pretty quickly as you will see in a bit.

new-load-test-wizard-step3

Once the load is setup, next step is to setup the Test Mix Model. Here we’ve to add the WebTest1.webtest to the mix.

new-load-test-wizard-step4

You can keep the defaults for the Network Mix, Browser Mix and Counter Sets. For the Run Settings, change the defaults to the following:

Load Test Duration, Warm-up duration: 20 seconds

Run Duration: 1 minute

Sampling Rate: 5 seconds

new-load-test-wizard-step6

Click Finish to finish setting up the Load Test.

Step 3: Double click on the LoadTest1.loadtest file to open the Test configuration.

launch-load-test

Step 4: Hit Ctrl+F5 to run the application without the Debugger (running tests with Debugger gives far worse results so start without Debugger).

Once IIS Express starts, start TaskManager and wait for the CPU spike to settle down.

Step 5: Now click on the ‘Run Load Test’ button to start the load test. Remember our code is synchronous at the moment. Once the test completes, you’ll get a test Summary as follows

test-results-synchronous

The important figures as I mentioned earlier are highlighted above. As we can see, Average Page Time is 3.23 seconds and we were able to serve up 79 pages in the 1 minute that the test ran (with progressively increasing loads from 1 user to 10 users).

This gives us a baseline. Now lets’ convert our entire call stack to Asynchronous code and run the load test again to see what kind of performance we gain.

Converting to an Asynchronous Implementation

We will convert our Web API Services and EF DB calls to async first. EF6 has provided Async counterparts to all calls that return Data (not the Query). The updated Api Controllers are as follows.

public async Task> GetNotes()
{
return await db.Notes.ToListAsync();
}

public async Task> GetTask()
{
return await db.Tasks.ToListAsync();
}

public async Task> GetBookmark()
{
return await db.Bookmarks.ToListAsync();
}

public async Task> GetEmail()
{
return await db.Emails.ToListAsync();
}

As we can see, we are awaiting the ToListAsync calls and we have annotated our Controller methods as async and returning a Task of the Enumerable instead of the Enumerable itself.

This converts the service calls into async but what about the client? It is still calling the service synchronously.

Converting the MVC Client to call the services asynchronously

We switch to our AsyncMvcClient project and get to the DashboardController. We first update the GetFeeds method to use the HttpClient() object instead of the WebClient object. The HttpClient has only asynchronous APIs, so we call the GetStringAsync API.

The keen eyed would note we are not awaiting the calls, so we are getting a Task from the GetStringAsync method.

Next we have a await for Task.WhenAll(…) and pass all the Task instances to it. What this does is, it retrieves all the Tasks and kicks them off together and waits for all of them to return. Once they all return, we create the Dashboard object and return it to the Action Method.

public async Task GetFeeds()
{
    var emails = new HttpClient().GetStringAsync("http://localhost:18545/api/Emails");
    var myTasks = new HttpClient().GetStringAsync("http://localhost:18545/api/Tasks");
    var notes = new HttpClient().GetStringAsync("http://localhost:18545/api/Notes");
    var bookmarks = new HttpClient().GetStringAsync("http://localhost:18545/api/Bookmarks");

    await Task.WhenAll(emails, myTasks, notes, bookmarks);

    Dashboard dash = new Dashboard();
    dash.Emails = Deserialize(emails.Result);
    dash.Bookmarks = Deserialize(bookmarks.Result);
    dash.Notes = Deserialize(notes.Result);
    dash.Tasks = Deserialize(myTasks.Result);
    return dash;
}

We update the Index action method to be called async as well and it now returns a Task instead of ActionResult.

public async Task Index()
{
    //Services.FeedService feedService = new Services.FeedService();
    Stopwatch timer = Stopwatch.StartNew();
    timer.Start();
    var feeds = await GetFeeds();
    timer.Stop();
    feeds.TimeTaken = timer.ElapsedMilliseconds;
    return View(feeds);
}

With that, we’ve converted our application to Async all throughout. Moment of truth, Time to test the outcome.

Load Testing Asynchronous implementation

Do a Clean Build and hit Ctrl + F5 to run the application without debugger. Open the LoadTest1.loadtest file and Run the Load Test.

test-results-asynchronous

As we can see, the Average Page Time is 1.17 and the Page Count is 254 - almost threefold increase if the number of pages hit and the 1/3 the Average Page time. This validates our belief, but there is more than meets the eye here.

Understanding Performance Changes

The improved scaling of the app resulting in the higher Count of requests handled is understandable, but how did the page time drop? Well, remember the bunching up of Tasks and then firing them off with the Tasks.WhenAll? Yup, that’s where I cheated by launching Async tasks in parallel (more threads so BEWARE). If I awaited each request to the web service, I would still get improved performance, but the Average Page Time would be similar to Synchronous operation as the following image shows (Page time of 2.92 and Request Count of 108).test-results-asynchronous-non-parallel

So yes, Async and Parallel Async are different and affects scaling and performance differently.

The Final Confession (I cheated, but only to prove a point)

Performance testing is tough, performance testing on a restricted VM is even tougher. To be honest, I couldn’t find enough performance difference for the Four Database calls (there is a caveat regarding database calls and async but that’s coming in a minute), hence to get some visible difference, I put a Thread.Sleep(500) for the synchronous calls and Task.Wait(500) for the asynchronous calls. Hence you see the 2 second+ Page times. Apart from making the performance difference obvious, I believe it doesn’t affect the conclusions.

- Synchronous calls to 4 services takes 2+ seconds and only 70 odd Page Requests were serviced

- Asynchronous calls with await for each service calls still takes 2+ seconds but handles 100+ Page requests.

- Asynchronous calls fired off in parallel take < 2 seconds (1.17 seconds) because there are 2 cores in action so effectively the 4 requests were handled by two parallel threads reducing the page load time and handling more Page Requests.

The Final Set of Caveats

Okay, so async and multi-threading usually gets all hairy and controversial if the caveats are not called out. I already called out one where the Task.WhenAll was potentially spinning up more than one physical thread to service one request. This can get dangerous and lead to Threadpool starvation in case of very high load.

Next, by making your Service calls async, you are moving the performance bottleneck from the Web Tier to the DB Tier (in our specific example). This could come back to bite you with increased DB server load. So use async on DB server cautiously.

However, if your services are going out to the Internet to fetch data (like from RSS feeds) you are good, because in that case, you will be firing more requests to an external service (which may be subject to its own rate limits) and won’t be killing any of your internal systems. In a nutshell, if the process is really long running and not stressing out some other part of your architecture, async is a good candidate to improving scaling of your web tier.

Conclusion

With that I conclude this article. Hopefully you’ve had a few takeaways from the demonstration, primarily:

- How to use aync APIs end to end starting with MVC Controller right down to Database calls via Async Web API services

- How to create a LoadTest project and fire load tests at your application

- Difference between scaling and performance gains when using Async.

Download the entire source code of this article here (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of DotNetCurry, DNC Magazine for Developers, SQLServerCurry and DevCurry. He has also authored a couple of books 51 Recipes using jQuery with ASP.NET Controls and The Absolutely Awesome jQuery CookBook.

Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.

Get in touch with him on Twitter @suprotimagarwal or at LinkedIn



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Tony Williams on Thursday, October 10, 2013 9:49 AM
There is no need to await/async if you're returning a task but doing nothing with the result

public async Task<IEnumerable<Note>> GetNotes()
{
return await db.Notes.ToListAsync<Note>();
}

could become

public Task<IEnumerable<Note>> GetNotes()
{
return db.Notes.ToListAsync<Note>();
}

And would function exactly as before but without the additional compiler re-writing it to a state machine.
Comment posted by Suprotim Agarwal on Thursday, October 10, 2013 12:00 PM
Good point Tony. However at the time of writing, my intention was to merely  highlight how EF6 provides Async counterparts to all calls that return Data. Yes I could have made the example more concrete by making use of that result.

Nevertheless you made an important point and thanks for the same!
Comment posted by Suraj Deshpande on Friday, October 11, 2013 12:01 AM
Loved this article. Great work..!!
Comment posted by Stephen Cleary on Friday, October 11, 2013 9:41 AM
1) Task.WhenAll does not spin up multiple threads.
2) "Thread.Sleep(500)" is not at all the same as "Task.Wait(500)"; you should compare "Thread.Sleep(500)" with "await Task.Delay(500)" for a more realistic load testing scenario.
Comment posted by Thiago Custodio on Monday, December 2, 2013 6:08 AM
Great article! Is there web performance and load test project for VS 2012?
Comment posted by stefanveliki on Tuesday, January 7, 2014 2:41 PM
Can you please fix your print style sheet so that I don't have to waste money on ink for all of the right hand side advertisements?
I would think that your print media would be the body of the article, not all that extra stuff.
Comment posted by Suprotim Agarwal on Tuesday, January 7, 2014 7:00 PM
Thiago: yes there is http://blogs.msdn.com/b/visualstudioalm/archive/2012/06/04/getting-started-with-load-testing-in-visual-studio-2012.aspx

Stefanveliki: I hear you loud and clear. Please bear with the design till March end as we are coming up with a major overhaul for the entire site. Printing will be fixed in that release.
Comment posted by yoluchi on Monday, November 24, 2014 2:03 PM
-->  stefanveliki <br>
$('sideRight').hide()  <br>
then print :)
Comment posted by yoluchi on Monday, November 24, 2014 2:05 PM
-- Correction
$('#sideRight').hide()