The async and await keywords in C# are an enormous leap forward in the ability to write asynchronous code that is responsive and maintains responsive user experiences, even in the face of operations that take a finite amount of time to complete. What sets this framework and language feature apart is its ease of use in achieving something that has traditionally been very difficult to do. The problem with it, however, is that the documentation available and the explanations you find are not easy to understand. There is much confusion about how to use these keywords and what they mean. With this post, I hope to provide that explanation of what is happening when using async and await in a way that is simple to understand and useful to get developers going quickly when they are not familiar with using this. I’ve had several people ask me about this recently, and I remember starting to look and async and await and feeling a bit mystified and like there wasn’t a good and quick way of learning to get the gist.
Step 0: What are Async and Await?
The async and await keywords go together like peas and carrots. It is not possible to use await without async, and it is generally inadvisable to use async without await. Async enables await and await makes asynchronous programming easy and accessible.
async
In C#, the async keyword is a method modifier. It is applied to a method to indicate that it can be invoked asynchronously. Really, this means that it can be awaited with the await operator. There is nothing more to it, other than changing how the compiler interprets return statements. Async enables the use of the await operator on what gets returned from the async method. The await operator can only be used with a method designated as async. async can only be applied to methods with a signature that follows certain conventions. Namely, they method must return void, Task, or Task<T>. We will get into why this is in a moment. For now, just understand that it’s a modifier for the method and the method must be constrained to certain return types. By convention, the names these methods typically end with Async, though there is nothing in the .NET Framework and/or C# language to enforce that. The simplest example with which I can come up for an async method is this:
private async void SomeFancyFireAndForgetMethod() { }
This is not a very good example, because a method returning void doesn’t demonstrate the use of async and await very well at all, but it is a simple example of a valid use of the modifier. Async methods retuning void are really just fire-and-forget methods. They return immediately and the caller does not have any knowledge regarding when or if they complete. In fact, such a method cannot even be awaited:
In general, it’s better not to use async methods returning void, as the C# compiler helpfully recommends. The most notable exception to this is to use them to be able to put async on an event handler in an application with user interface events because these methods must return void, but in order to be able to await async methods inside these event handlers, they need to be modified with async. Otherwise, you should return Task or Task<T> from your async methods. Better examples of something more useful would be:
private async Task SomeFancyMethodAsync()
{
}
private async Task<int> SomeFancyMethodWithAReturnValueAsync() { return 42; }
Note that these methods appear to use (and not use) return in a funny way. The first is a method has a (non-void) return type, yet there is no return. The second has a Task<T> return type, yet the return statement simply takes a value of type T (in this case, T is int). This is because the method will actually return when an await is invoked inside the method (this is not pictured in these overly simple examples). When the Task that gets returned at the time of an await statement is then awaited in the calling function, the Task given to the await operator will return the value returned from the called async method.. The meaning of this should become more clear as we continue with further examples. For now, notice that the type that should be returned from these functions is the type of the generic paramenter for the declared Task return type, This also means that nothing should be returned in the case of declaring a nongeneric Task return type.
Note also that async can be applied not only to named methods, but to anonymous functions (including lambda expressions) as well.
var someTask = new Task(async () => await Task.Delay(1000));
await
In C#, the await keyword is an operator that can be applied to a an async Task. The result of applying this operator is that execution of a method will suspend until the completion and return of the Task in question that was returned from an async method. An example of using await would look like this:
var returnValueFromSomeLongRunningOperation = await SomeFancyMethodWithAReturnValueAsync();
To Start: Using an Async Method:
The most useful and magical part of using async and await is consumption. To call an async method, you call it just like you would call any other method. It is in what you do with the returned value that gets different and interesting. When calling an async method, the method will return as soon as it has completed its preliminary work and awaited some other async method and the calling method can then continue executing. What is returned at this time from the method is a task you can later await to get the result (if there is a result – and result could mean an exception is thrown) and suspend execution of the calling method until execution has completed. Consider, please, the following code:
using System; using System.Diagnostics; using System.Threading.Tasks; using NUnit.Framework; using Should;
namespace AsyncAwaitTest
{
[TestFixture]
public class AsyncAwaitDemo
{
[Test]
public async void AsyncMethodReturnValueShouldReflectOnAwaitingReturnedTask()
{ var state = 0;
var stopWatch = new Stopwatch();
stopWatch.Start();
var someTask = new Task(async () => await Task.Delay(1000));
await someTask;
var asyncTask = SomeFancyMethodWithAReturnValueAsync();
// calling the async method should return immediately despite the one second delay in the method
stopWatch.Elapsed.ShouldBeLessThan(TimeSpan.FromMilliseconds(20));
// calling the async method should not have altered the state variable until awaiting the result
state.ShouldEqual(0);
state = await asyncTask;
// it is now, having awaited the Task, that delay should be observed and the value returned
stopWatch.Elapsed.ShouldBeGreaterThan(TimeSpan.FromSeconds(1));
state.ShouldEqual(42);
}
private async Task<int> SomeFancyMethodWithAReturnValueAsync()
{
await Task.Delay(1000);
return 42;
}
}
}
Ignore, for the moment, the async method itself. Just know that it returns a Task<int> and that it takes a full second to complete execution. Focus instead on the test making assertions based on what happens when calling this method. What you want to notice here is that delay of a full second is not observed and the local state variable is not updated with the return value from the async method call until the returned Task<int> is awaited. You may immediately realize that there is no assignment to the local state variable until the call to await and argue that of course it is not going to change because of that. To demonstrate this further and to show dealing with an async method that returns Task instead of Task<T>, I’ll go ahead and add another method to this class with another Test. Instead of using a local variable for state, I’ll now use side effects from the method. I am not arguing that this is good code, merely demonstrating further what is happening with async and await:
private int _state;
[Test]
public async void AsyncMethodSideEffectShouldReflectOnAwaitingReturnedTask()
{
_state = 0;
var stopWatch = new Stopwatch();
stopWatch.Start();
var asyncTask = SomeFancyMethodAsync();
// calling the async method should return immediately despite the one second delay in the method
stopWatch.Elapsed.ShouldBeLessThan(TimeSpan.FromMilliseconds(20));
// calling the async method should not have altered the state variable until awaiting the result
_state.ShouldEqual(0);
await asyncTask;
// it is now, after having awaited the result that the delay should be observed and the value returned
stopWatch.Elapsed.ShouldBeGreaterThan(TimeSpan.FromSeconds(1));
_state.ShouldEqual(42);
}
private async Task SomeFancyMethodAsync()
{
_state = await SomeFancyMethodWithAReturnValueAsync();
}
This should demonstrate more convincingly that execution of the method calling an async method continues before the async method returns.
Important Distinction: Do not confuse suspension of execution with blocking
In the code we have seen so far, each of the methods calling and awaiting an async method have been async themselves. This is required by the framework. I found it confusing when I first encountered async and await that in order for a method to await an async method, the calling method itself also has to be async.
I think understanding this correctly is the key to understanding how await works and how to properly use it. Notice that the compiler generates a useful error in failing the build. This is useful and great and offers suggestions on how to proceed, but it doesn’t tell us anything about why. Why does the calling method have to be async? The answer lies in understanding that if one assumes that await causes the thread executing the calling method to block, one is operating on a faulty assumption. MSDN rescues us from this mistaken belief in the documentation on await. Here we find:
An await expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method as a continuation on the awaited task. Control then returns to the caller of the async method. When the task completes, it invokes its continuation, and execution of the async method resumes where it left off.
It is for this reason that a calling method must be async itself. When a method marked with the async modifier is called and it in turns awaits another async method, control is returned to the calling method to continue operation until it awaits
the result of the called method. Ultimately, this can and should lead to a chain of async methods calling async methods with automatic suspension and continuation and completion and asynchronous, non-blocking operation. Stephen Cleary likened this to the old story and nerd favorite about Turtles All the Way Down in an excellent MSDN article. Think about awaits in your code as a line of demarcation where what happens in your method after the await becomes like a delegate passed to the called async method, which it will execute on completion
of itself. It’s like being a bossy spouse manager and saying to the method you are calling: “Do whatever it is you do with these input parameters and when you are done, here is another list of things for you to do.”
This has been a quick introduction to using async and await. In my next post, I’ll go deeper in explaining the concepts with an example. Please stay tuned.