View on GitHub

anthonysteele.github.io

Bloggy

Avoiding simple mistakes in async await

I see a lot of code lately that makes some simple mistakes using the async ... await construct in C# code.

TL;DR

Async can’t make your code wait faster, but it can free up threads for greater throughput. However some common antipatterns prevent that from happening.

More and more libraries are going to support async, and in some cases support only async. So we need to get used to doing async, and get used to doing it competently.

A Task is a Promise

Think of Task<Order> as a promise of an order later, i.e. “There will be an order. If I don’t have it now, I’ll have it at some point in the future”. The Computer Science term is “future” or “promise”.

Since this is C#, there are a few other things that can happen besides the promised order arriving: The value can arrive, and be null instead of an order. Or an exception can be thrown instead.

The language support for async ... await is about allowing you to write code in the familiar linear style, but under the hood, it is reconfigured so that the code after the await completes is put in a callback that is executed later. Without async ... await we could still write equivalent code using callbacks, but it was just much harder.

This is not unique to C#. Look at how this Javascript promises library works with chained callbacks. And futures and promises in Java.

There is no thread

A Task - the promise of a value later - is a general construct which says nothing about how the value is being generated. It’s usually network I/O, and there is no thread waiting as there would be for computation.

Use await not .Result to get the result of a task

Most of the time you should not need to use .Result. Let the async flow. This means that lots of methods have to be sprinkled with async, Task and await: the caller, the caller’s caller and so on up the chain. This is just the cost of it, so get used to it. The model is not so much an “some async methods” in an app as “an async app”.

Calling .Result forces the code to wait at that point, losing the main advantage that you can hand off whole blocks of code to be executed later when results are available.

I heard Kathleen Dollard compare the async call stack to a Light tube: Any blockage between the basement and roof will prevent light getting through. And with async, any blocking code will prevent the task from getting through.

Liberal use of .Result is red flag. Try moving the .Result up to the calling method. I generally do this while refactoring out .Result calls as it’s a step towards the goal. It will eventually become apparent if your whole operation can be async or not.

Ideally, you hand the async Task off to your framework. You can do this in ASP MVC and WebApi as they allow async methods on controllers, you do this in NUnit as tests can be async, in NancyFx, OpenRasta, etc.

Avoid Task.Run

After .Result, the second most common way to stop being async is Task.Run. It’s also a bad idea for much the same reasons.

Task.Run Can be used to re-synchronise code when needed. But you the first option should be to avoid doing this; and if you do, it’s not the best way. It is a blunt instrument. Performance will be worse than if the code was not async at all, since you effectively launch an additional thread from the thead pool, and wait for it. Two threads are kept busy for the duration.

And there are problems with other people reading the code: One possibility is that it will be removed by experienced engineers as obviously unnecessary and poorly performing code.

Another possibility is that engineers will misapprehend that Task.Run is necessary to make async work, and scatter it liberally where it isn’t needed. I’ve seen this.

In async code, you do not need Task.Run to start a task since The task returned by an async method will be “hot”. i.e. already started.

This of course makes removing the Task.Run like Russian Roulette: most are harmless, but a few are deadly.

Avoid async void methods

The type async void is only there for compatibility reasons. When you can, use e.g. public async Task Foo() instead of public async void Foo().

There are patterns for converting code to async. e.g. private bool SomeTest() becomes private async Task<bool> SomeTest(). bool becomes Task<bool> and void becomes Task - and where there is no return value we still need a task so that the caller can know when the async code has completed without a return value. You only drop the Task when it is necessary for a calling framework that specifically needs a void return.

In async code, you should await wherever possible

I asked the stupid question so that you don’t have to: In server code, if you use one await for a slow operation, should you use a second await for a fast operation?. And the answer is “yes”. If you have one await in your code, then you might as well use as many as you can to chop the operation up into small parts so that operations can flow through better. So if your code can trivially await an async method instead of a calling the sync version, then you should do so. There is no additional performance penalty to this.

In UI code, there is a guideline that “operations that could take over 50ms should be async” so as to not lock the UI thread. But it’s clear that this does not apply to web server code where it’s all thread pool threads already. Even quicker operations should also be awaited in order to break code into smaller chunks and so increase throughput.

Further reading