As you probably already know, all the ASP.NET Core Identity system API's relevant methods are asynchronous, meaning that they return an async Task rather than a given return value; for that very reason, since we need to execute these various tasks one after another, we had to prepend all of them with the await keyword.
Here's an example of await usage taken from a typical action method:
await _userManager.AddToRoleAsync(user_Admin, role_RegisteredUser);
The await keyword, as the name implies, awaits the completion of the async task before going forward. It's worth noting that such an 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, thus returning the thread control to the caller. Eventually, when the task completes, it invokes its continuation, thus resuming the execution of the async method where it left off.
That's the reason why the await keyword can only be used within async methods; as a matter of fact, the preceding logic requires the caller to be async as well, otherwise, it wouldn't work.
Alternatively, we could have used the Wait() method, in the following way:
However, doing that is not considered to be a good practice - for good reasons. In the opposite way to the await keyword, which tells the compiler to asynchronously wait for the async task to
complete, the parameterless Wait() method will block the calling thread until the async task has completed execution; therefore, the calling thread will unconditionally wait until the task completes.
The ASP.NET Core TAP model
To better explain how such techniques impact a typical .NET Core application, we should spend a little time to better understand the concept of async tasks, as they are a pivotal part of the ASP.NET Core Task-based Asynchronous Pattern (TAP) model.
One of the first things we should learn when working with sync methods invoking async tasks in ASP.NET is that when the top-level method awaits a task, its current execution context gets blocked until the task completes. This won't be a problem unless that context allows only one thread to run at a time, which is precisely the case of AspNetSynchronizationContext. If we combine these two things, we can easily see that blocking an async method (that is, a method returning an async task) will expose our application to a high risk of a deadlock.
What's a deadlock?
A deadlock, from a software development perspective, is a dreadful situation that occurs whenever a process or thread enters a waiting state indefinitely, usually because the resource it's waiting for is held by another waiting process. In any legacy ASP.NET web application, we would face a deadlock every time we're blocking a task, simply because that task, in order to complete, will require the same execution context of the invoking method, which is kept blocked by that method until the task completes!
Luckily enough, the "good old" (well, not so much!) ASP.NET has been replaced by .NET Core, where the legacy ASP.NET pattern based upon the SynchronizationContext has been replaced by a contextless approach layered upon a versatile, deadlock-resilient thread pool. This basically means that blocking the calling thread using the Wait() method isn't that problematic anymore; therefore, if we choose to use such approach instead of await like we did early on, the method would still run and complete just fine.
However, by doing so, we would basically use synchronous code to perform asynchronous operations, which is generally considered a bad practice; moreover, we would lose all the benefits brought by asynchronous programming, such as performance and scalability. For all those reasons, the await approach is definitely the way to go there.
For additional information regarding threads, async tasks awaits, and asynchronous programming in ASP.NET, we highly recommend checking out the outstanding articles written by Stephen Cleary on the topic, which will greatly help in understanding some of the most tricky and complex scenarios that we could face when developing with these technologies. Some of them were written a while ago, yet they never really age:
- Async and Await
- Async/Await FAQ
- Don't Block on Async Code
- Async/Await - Best Practices in Asynchronous Programming
- ASP.NET Core SynchronizationContext
Also, we strongly suggest checking out this excellent article about asynchronous programming with async and await: