Cloud

Ins and Outs of async and await

C# 5.0 introduced two new keywords: async and await. These keywords have a very powerful effect that can be used without fully understanding them. This is a double-edged sword. It’s great to have a language feature that doesn’t take much time to implement, but at the same time, if there isn’t at least a basic understanding, then utilizing it correctly and troubleshooting any issues around it becomes very difficult.
It’s outside the scope of this blog post to do a deep dive into what exactly is happening when using these keywords, but hopefully you will have a better understanding, conceptually, so that you can leverage their functionality in an intelligent way.

async

The best way to describe the async keyword is that it is an implementation detail. This means that although it is used when declaring a method, it is not part of the method signature itself. Any code that invokes a method marked as async doesn’t know, nor does it care, that the method has been marked this way. async is really a way of indicating to the C# compiler that this method needs to be compiled in a different manner than a typical method.
To drive the point home that async is an implementation detail, think about it this way: async is not valid to use on a method when you are defining an interface. If you write a class to implement the interface, async is not included when you have Visual Studio stub out the interface implementation.
When should you mark a method as async? The answer is very simple: whenever the keyword await is used in the body of the method. If you mark a method as async and at least one await is not present, a compiler warning will be generated. The compiled code will still execute, but the method can be compiled in a more optimized way if the async keyword is removed.

await

The next question is naturally “when do I use await?” await can be used with any awaitable type (duh!), but what is an awaitable type? Many developers would answer: System.Threading.Tasks.Task or System.Threading.Tasks.Task<T>. It is correct that those are two awaitable types, that is not the complete answer. The complete answer is a little more complicated. Whether or not a type is awaitable is determined by duck typing.
The duck typing rules (simplified) are that the type you want to await must have a public method called GetAwaiter that takes no parameters and returns an object that meets the following criteria:

  • Implements either of the two interfaces: INotifyCompletion or ICriticalNotifyCompletion (both are located in the System.Runtime.CompilerServices namespace)
  • Has a property called IsCompleted that returns a boolean
  • Has a method called GetResult. The method is not required to return anything (i.e. can be void)
Microsoft - The Essential Guide to Microsoft Teams End-User Engagement
The Essential Guide to Microsoft Teams End-User Engagement

We take you through 10 best practices, considerations, and suggestions that can enrich your Microsoft Teams deployment and ensure both end-user adoption and engagement.

Get the Guide

All that being said, Task and Task<T> pass the duck typing test, and are likely going to be the only awaitable types you’ll encounter.
By convention, all methods that return an awaitable type should have the suffix of Async at the end of the method name. This is not a requirement, but it does make it obvious to anyone who sees the method (either in code or via intellisense) that it returns an awaitable type.
Please note that at no point have I said “an awaitable method”. Methods are not awaitable, types are awaitable. The most common use of the await keyword will look something like this:

var value = await SomeMethodAsync();

While it looks like the SomeMethodAsync method is being awaited, it is not, the object it returns is being awaited.

When and How to use await

Just because a method returns an awaitable type doesn’t mean the await keywords should be used. The await keyword should only be used when the result of the awaitable type is needed or if the completion of the asynchronous operation represented by the awaitable type is required before continuing execution of the current method. Often the result is needed right away, but this isn’t always true. Take the following example:

var value1 = await GetValueAsync();
var value2 = await GetAnotherValueAsync();

A first glance, there may not be anything wrong with the above code, but there are potential performance problems. Let’s say GetValueAsync() takes 4 seconds to complete and GetAnotherValueAsync() takes 3 seconds to complete. Executing both lines of code will take approximately 7 seconds to complete. This is because the await keyword causes the current method to suspend execution and the method will not resume executing until the asynchronous operation is complete. Assuming these two asynchronous methods are completely unrelated, there is no need to wait for the first one to complete before executing the second one, so the above code can be optimized by rewriting it this way:

var awaiter1 = GetValueAsync();
var awaiter2 = GetAnotherValueAsync();
await System.Threading.Tasks.Task.WhenAll(awaiter1, awaiter2);
var value1 = awaiter1.Result;
var value2 = awaiter2.Result;

There is a little more code, but in this case, the method will only take about 4 seconds to execute, because both asynchronous operations are happening in parallel. We could have awaited each awaiter individually, and nothing would have been wrong with that, but Task has a static method that makes it easier to combine multiple awaiters into a single awaitable. To get the result of each asynchronous operation, invoke the Result property on the awaiter. Please note: Invoking Result on the awaiter before the asynchronous operation completes will cause the current thread to block until the operation has completed, so please only do this once you know the awaitable has completed. Using the await keyword gives us this guarantee, so we know it is safe to call after it has been awaited.
If the method you are writing doesn’t have a need for the result of an asynchronous operation nor does it need a guarantee that the operation is complete before it finishes execution, then there may be no reason to await the operation at all. One common reason to await an operation even if the result is not needed: the method should handle exceptions that occur in the asynchronous operation. If this isn’t needed , then it’s entirely possible that the method may not need to await anything, and if that’s the case, the method itself may not need to be marked as async. Note: Even if a method is not marked as async, it should still have the Async suffix if it returns an awaitable type.
Hopefully this blog post has given you a better understanding of when and how to use async and await, and will save you time in the future when working with this language feature.

Tags

About the Author

More from this Author

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to the Weekly Blog Digest:

Sign Up