C# 5.0 introduced two new keywords:
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.
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.
The next question is naturally “when do I use
await can be used with any awaitable type (duh!), but what is an awaitable type? Many developers would answer:
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:
ICriticalNotifyCompletion(both are located in the
- Has a property called
IsCompletedthat returns a boolean
- Has a method called
GetResult. The method is not required to return anything (i.e. can be void)
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.
All that being said,
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
await, and will save you time in the future when working with this language feature.