The .NET 4.5 async/await feature in Promise and Practice

The .NET 4.5 async/await feature provides an opportunity for improving the scalability and performance of applications, particularly where tasks are more effectively done in parallel. The question is: do the scalability gains come at a cost of slowing individual methods? In this article Jon Smith investigates this issue by conducting a side-by-side evaluation of the standard synchronous methods and the new async methods in real applications.

The performance of async/await in Entity Framework 6 and ASP.NET MVC5

No one wants to build a slow, unresponsive application, but it takes extra development time and effort to make applications run fast. Someone, therefore, has to make a decision on how much time and effort should be applied to maximizing performance. The release of the .NET 4.5 async/await feature has radically changed the effort/reward ratio of one small, but important part of the performance trade-off. It is now much easier to write asynchronous code in C#.

In this article I will explain what async/await can do to improve performance and scalability and will provide links to more documentation. I will then spend the bulk of the article going through my own experience of developing an open-source library which has normal, synchronous, and asynchronous versions of each method. I end by sharing with you the actual performance gains/losses I obtained in real applications using synchronous and asynchronous versions of each method and my views on where the new async/await  feature can be used with most gain.

The performance problems that async/await solve

The async/await feature solves three performance or scalability problems:

  1. They can make your application handle more users.
    Most multi-user applications, such as web sites, use a thread for each user session. There are a lot of threads in the thread pool, but if lot of users try to access the site then they can run out, which in turn leads to blocking. Also there is a memory cost for each thread started, see Using Asynchronous Methods in ASP.NET MVC 4
    If you have requests that access an external resource such as a database or a web API then async  frees up the thread while it is waiting. This means you will use fewer threads, and so avoid reaching the maximum number of threads so quickly and use less memory as well.
  2. You can process multiple I/O bound methods in parallel
    If, for example, you needed to access a number of remote services either in legacy systems or over the web and then combine their results to show the user. If this were done synchronously, the process would take the sum of the access times for each service. With the new async/await feature you can easily run them all in parallel and then it takes only as long at the longest access-time. See an example at Performing Multiple Operations in Parallel .
  3. You can make your interface more responsive to the user.
    If you have a request on a site that is slow you can make the site more responsive by starting the action asynchronously and returning to the user before the action has finished. There are some issues around handling errors if the asynchronous task fails, but for places where your application interfaces to an external system that is slow this can be worth the effort.

In reality you could have done all of these enhancements before the async/await feature came along using the previous .NET asynchronous methods. However these previous async implementations were complicated and not very obvious, so developing asynchronous code with these older methods was hard work. What the new async/await does is make writing asynchronous methods really simple using just a few lines of code. The only down side of this is there are a few subtleties you need to know about which I will point out later.

If you would like more information on async/await then here are two useful Microsoft links.

And on Simple-Talk

Other libraries updated to support async/await

I should also mention that many other system functions within .NET have now been updated to have async versions of their methods such as HttpClient with methods like GetStringAsync. Also a lot of other libraries and frameworks have also been updated such as Microsoft’s Entity Framework 6 (EF6), ASP.NET MVC4 and 5, SignalR 2 etc.

This sweeping change is important as it means that async can now be used throughout an application. In this article I will spend some time looking at using async/await in EF6 and MVC5 applications.

My experience of using async 

I was personally very pleased to see the new feature because I build data modelling and visualisation applications. These applications have three groups of features that particularly benefit from using asynchronous methods:

  • Complex database calls that take seconds, in a few cases minutes, to return.
  • Access to RESTful geocoding web services that are slow to respond.
  • Long-running modelling actions that need to run for some minutes.

I did my research and I have built an open-source project called GenericServices which contains both synchronous, i.e. normal, and async versions of each method so I am really interested in the performance difference.

A Database Example

Let us start with a simple example using EF6 with ASP.NET MVC5. Below are two methods that return a list of Posts in a fictitious blog site. The first is the normal, synchronous method and the second is the asynchronous version.

You’ll notice immediately how similar they are. I have highlighted the changes in the async version in yellow. The async version is not hard to write and it is easy to understand. Before async/await you would have needed two methods for the async version, linked by an event.

Here are the two different calls inside an ASP.NET MVC web application, again with the changes in the async version in highlighted in yellow.

This shows how simple it is to implement async methods with async/await. Previously you would have needed two methods in MVC for handling async access, again linked by an event and a few other obscure methods. (See Dino Esposito’s article in 2012 on async in ASP.NET before .NET 4.5 https://www.simple-talk.com/dotnet/asp.net/asp.net-go-async-or-sink/).

A web service example

Although the interface with the database is a common use for asynchronous methods, the place where async/await will really shine is on slow web services. Here is an async example of an access to the BBC’s trial radio schedule service. See the full code at GitHub: JonPSmith/SampleMvcWebApp.

This service is fairly slow at around 500ms. so you gain significant scalability improvements by using an async task because it releases the thread for other sessions while it is waiting.

Things I learnt about async/await

Here are a few things I learnt alone the way which might help you get started.

1. You need to go Async all the way

I found async/await difficult to get started with because you can only use an await inside a method that is marked with async and returning a Task or Task<>. Then the only way of using that method is to use an await, which needs a async … This all felt somewhat recursive until I realised that the async needed to go all the way up the chain.

This means that once you have an async method somewhere then you are going to have async methods all the way up to the top. In the examples here the ‘top’ is an ASP.NET MVC5 application, which can handle async. Many other Microsoft frameworks have similarly been updated to take async methods, and thankfully my favourite Unit Testing framework, NUnit, also supports async too.

2. You can mark a method as async even if an async method might not get called

This goes with the first item. If you might conditionally call an async method then the calling method still has to be marked as async. However if you do not call that method then the method is run synchronously and returns a Task which does not wait for anything, which is just want you want.

3. Do not have a void return from async method

Watch out for having a void return on an async method. It is valid, but it is a rather special use (see the link below to Stephen Cleary’s article for more). However I found to my cost that when you are converting code to async it’s easy to forget to replace the void return with Task, and then you get into all sorts of problems. I did this inside a SignalR Hub and my application had a strange error that was really hard to find.

4. Watch out for your context

When starting an async task it copies the current ‘context’ so it can restore the context when it exits. This means if you are running in a Windows 8 and ASP.NET system the code can refer to the items it needs to show the results. However this context can be large, so if you have async methods that complete without needing the context then append .ConfigureAwait(false) to the end of the async method you are calling.

I recommend an excellent article by the expert, Stephen Cleary, which deals with all these points and more. The article is called  ‘Best Practices in Asynchronous Programming’

It is simpler, but is it better?

Although it is now really simple now write async methods, do they improve performance? I found a number of articles written by well-known Microsoft people that said there was a big setup cost to async methods and you should only use this where needed. However a number of these were old, i.e. before EF6 was released. I therefore emailed one of the main Microsoft people and his reply was “With EF6/async, you use async everywhere – that changes everything“.

I was already doing extensive performance comparisons to ensure my open-source GenericServices library is fast. I therefore extended my testing to side-by-side comparisons of sync and async versions of the EF’s CRUD (Create, Read, Update and Delete) features. I found the results to be much better than I expected.

In the next two sections I show the side-by-side performance of synchnonous and async methods on a EF6 database and then the same database EF6 running in an ASP.NET MVC5 application. If you just want the recommendations based on these findings then you can skip the next two sections.

A. Side-by side Performance of EF6

For my testing I built a blog database consisting of three classes: Blog, Post and Tag. The diagram below shows the data classes and their relationships.

2022-tagpostblog-3732ff50-31ac-429e-a91a

In this article the tests are done on the Post class, which is the more complex than the Tag or Post. Also, for update I assume a disconnected update, i.e. as would happen on a web site, where the original Post is read and then specific properties are updated before they are written back to the database.

The raw comparison of the sync and async EF6 methods was done inside an NUnit test running inside Visual Studio 2013 on my development PC which has an Intel i7 processor clocked at 3.31GHz. The database is localDb running SQL server 2012. I ran 1000 tests on a database filled with 1000 rows and averaged out the time for each method. The list below gives the average time for a single operation in milliseconds.

 

  Sync (ms) Async (ms) Diff   Notes
List, Direct 2.80 7.80 279%   Just reads Post class
List, DTO 16.80 21.00 125%   Reads Post, Blog and Tags
Create 10.40 8.80 85%   Reads Tags and write Post
Update 15.70 9.70 62%   Reads Post, Tags and write Post
Delete 0.90 1.10 122%   Simple state update

My analysis of these results is as follows:

  1. Async does not have much of an overhead
    I was surprised that the async versions were so fast. Lots of blog posts warn about async being slow, but on the simplest method, which was listing the Post class it was only 5ms slower. That, I think is impressive. However in the unit tests the context it was saving was small (see earlier in the article to learn more about context) and it also caches the context so one off methods might take longer. I look at single reads later in the real-world section below.
  2. Some of the async methods are faster!
    You can see that the async version of create and the update are faster (see blue percentages). Why is that? I am not totally sure, but I think it is because there are multiple database accesses and because I am using Task.WhenAll method which allows parallel running. I would be interested in anyone’s insights on this.

Note the test code is part of my GenericServices open-source project so you can look at the actual code. The Performance test method is called Perf21NCompareEfSyncAndAsync1000Ok and can be found on GitHub at GitHub: JonPSmith/GenericServices

B. Side-by side Performance of EF 6 in ASP.NET MVC5 web site

Raw figures as shown above are interesting, but it is real-world performance that matters. I have a companion MVC5 web site called SampleMvcWebApp, also open-source, which I try things out on. This has the same Blog, Post, Tag format described at the start, but with only a few entries (default 17). I have this MVC web app on different hosting environments, plus internally:

  1. A shared site running Windows Server 2012 R2 through a low-cost hosting company in the UK called WebWiz.
  2. A Windows Azure account using a standard single instance.

My tests are done using ab, the Apache HTTP server benchmarking tool. This allows web site performance tests with different mixes or concurrent users. I used the benchmark tool to read the posts list (17 entries) in two modes: a) one user trying 100 times, b) 50 users trying twice, all at the same time. The results are shown below with the Sync (ms) and Async (ms) columns showing the average total time for one list operation. Note that the SD (ms) column holds the standard deviation, which is the amount of variability in the time that each list took.

 

Host+ action Sync (ms) SD (ms) Async (ms) SD (ms)
WebWiz        
– List Posts ave(100), one user 90 4 95 9
– List Posts ave(100) , 50 users 1065 450 1050 450
Azure, standard        
– List Posts ave(100), one user 110 11 120 50
– List Posts ave(100) , 50 users 1200 500 1200 500

Note that you can try the WebWiz site yourself as it is live at http://samplemvcwebapp.net/

My analysis of these results is as follows:

  1. Async listing is slightly slower for one user.
     There is a small difference, less than 10%, between the sync and async versions. However this must all be due to the use of async as the data is the same. It amounts to 5 to 10 milliseconds, which is a little more than we saw in the raw EF figures earlier.
  2. Using Async seems to have had no effect on the scalability of the web site.
    You will see that the figures for 50 simultaneous users are almost the same for both sync and async methods. That makes sense as the database is small and the query very simple so there is almost no waiting for the database (raw numbers for the database access part on small databases is 2 to 3 milliseconds). Clearly with much more complex data accesses then async would start to pull away in terms of scalability of the web site because the thread would be freed up for longer while the database is doing its work.
  3. Most of the time is spent in the MVC/Http part of the process.
    If you look at the timings for one request captured using Google Chrome’s developer tools you can see the parts of the timeline below:

2022-e21cb83f-2268-416a-83ce-30b3b4e8b0e

C. Effect of async/await on web Scalability

As explained in the introduction the primary reason for using async methods is to improve web scalability, i.e. the application is able to handle more users because async methods release threads while waiting. Because of the design of async methods we know this is true, but the question is: how much does this help in the real world?

I should start by saying that the main focus on my investigations was to estimate the impact of using async methods. For this reason I looked at individual database access method, which by their nature are compute bound and therefore rarely wait for any resources. You will have seen this in the figures in the last section, as fifty simultaneous users using async data access was exactly the same time for sync and async methods.

Therefore to test scalability I used two method, one synchronous and one async, that delayed by 500 milliseconds. I ran this on the WebWiz hosted site using the ab, the Apache HTTP server benchmarking tool, looking at twenty simultaneous users. The async accesses where fairly consistent at 560 milliseconds (ms.), which is what I expected, i.e. no slow down for multiple users because the async delay method is releasing the thread quickly. On the other hand the sync version was very variable with the quickest being 800 ms. and the longest taking over 2 seconds. This shows that the web site is slowing down even with just twenty users when a synchronous delay method is used. A simple test but I think it shows the power of async.

I would recommend two videos by Microsoft people that provide much better explanations and demos of the effect of using async methods on web site performance and scalability:

Recommendations based on real-world tests

Taking the output of the test tests above and some additional tests I have performed on async tasks (also available to try under the menu item ‘Actions’ on the site SampleMvcWebApp) then here are my recommendations based on these results.

  1. Database – worth using async, but not a lot of gain for simple accesses
    The overhead of using async is small: in my real world tests something like five milliseconds, maybe ten if the context is large. Running side-by-side testing on an ASP.NET MVC5 shows a small difference between sync and async method for small database accesses, i.e. they approximately took about the same time and there was little gain on scalability. This is because, for small database accesses, most of the time is spent computing rather than waiting.
  2. Web services, external APIs, etc. – definitely use async.
    Async is definitely worth having for slow web services, APIs etc. This is because the thread is released for a long time, hence allowing the application to handle more users.
  3. Parallel running of Web services, etc.no-brainer, use async every time.
    Using parallelism to improve performance is great if the task at hand can run in parallel, but this sort of problem does not happen often. I do have a few cases that would have benefitted from this: I had lots of processing to do on results from a geo web service. By using parallelism I could process the current result while fetching the next result. There is also a link to an example in the introduction of this article.
  4. What about compute-bound tasks? – the documentation says use async
    Async/await is now the recommended way of running any multi-tasking code, whether its I/O bound or compute bound task. Async/await is simpler to write and it has a better interface than some of the older tasking methods such as BackgroundWorker. See Microsoft documentation Asynchronous Programming with Async and Await (C# and Visual Basic): Threads

Any down sides?

The one down side I should talk about is debugging. Async tasks are definitely more complicated to debug. For complex problems I sometimes drop back to normal sync versions first to ensure the basic idea is right before going back to async. I have found Unit Testing helps a lot and both NUnit and MSTest (and I expect others) now support async methods.

This isn’t a down side, but more a warning that async/await is not a golden bullet. Async/await is just one of many patterns and techniques to help you improve the responsiveness and scalability of your applications. If you haven’t looked at caching, compression, CDNs, etc. then they will definitely gain you more than swapping all your database accesses to async/await. See the timing trace from the ASP.NET MVC5 test web site above to prove this point.

Dino Esposito is currently running a series on Simple Talk about scalability. Check out his first post at ASP.NET Scalability: the Good, the Bad and the Hype and follow on the articles as they go.

Conclusions

I think the new async/await and supporting library changes in .NET 4.5 makes the job of implement tasking/parallelism so easy that I expect to see it used a lot in the future. For some situations it will make a big effect on performance and scalability of applications. In other situations it may slow the operation down a small amount, so you need to decide whether that matters or not.

The addition of async methods in Entity Framework 6 and in many other Microsoft application frameworks makes using async/awaits an even more compelling proposition. This is definitely a good step forward and I am already enjoying using it.

I have developed two open-source projects which make full use of async/await that you might find useful to look at. They are:
1. SampleMvcWebApp ASP.NET MVC5 web application
This is a live site with a fictitious blog and some examples of long-running actions. See http://samplemvcwebapp.net/ for live site and https://github.com/JonPSmith/SampleMvcWebApp for source on GitHub.
2. GenericServices .NET library
GenericServices is a .NET class library which helps developers build a service layer.  SampleMvcWebApp uses this library extensively. GenericServices is available on GitHub at https://github.com/JonPSmith/GenericServices