Writing asynchronous code after the introduction of async/await in .NET4.5 became relatively easy. Async/await keywords improve code readability and programmer productivity as code resembles synchronous code and it's easy to follow, thanks to the compiler taking care of the most difficult part of asynchronous programming.
Let’s create an example to see how easy it is to write async code to curl a specific URL and return its content as a string.
We have our asynchronous call that does not block the calling thread while it fetches the content of Bynder.
In an ideal world people would always use our library in the following way:
However, the programming world is far from ideal and some people might use it in the following way:
This way curl is done in a synchronous way, blocking the calling thread until curl finishes. If this is executed in a Console Application, most of the time our code will run as expected (not necessarily always).
However, if that code is run in a UI Application, for example when a button is clicked like in the following example:
Then the application will freeze and stop working, we have a deadlock. Of course, users of our library will complain because it makes the application unresponsive.
To solve the problem and make our library work in this situation we will have to rewrite our function as follows:
In fact, only adding the first ConfigureAwait(false) would be enough to solve the problem.
In conclusion, it is good practice to always use ConfigureAwait(false) in your library code to prevent unwanted issues.
Now, we will analyse why a deadlock occurs in an UI application (and not in most console applications) and why ConfigureAwait(false) solves this issue.
First we need to understand how most UI applications work:
- There is one thread that is responsible for the UI: the UI Thread. The UI can only be updated if called from this thread, so if this thread is blocked, the application becomes unresponsive.
- The UI Thread has a message queue to receive notifications/actions to perform. In Win32 this translates into something like this:
- The UI Thread has a SynchronizationContext by default.
If there is a SynchronizationContext (i.e. we are in the UI thread) the code after an await will run in the original thread context. This is the default and expected behaviour.
If we were to modify a UI component from a thread other than the UI Thread, a ‘System.InvalidOperationException’ would be thrown, like in the following example:
Back to our working example, our async DoCurlAsync call is conceptually equivalent to:
NOTE: this snippet is a simplified version of what actually takes place when using await. It also does not take care of the using keyword for closing resources.
The Post call sends a message to the UI Thread message pump to be processed, so to finish DoCurlAsync it is mandatory that the UI Thread executes await httpResonse.Content.ReadAsStringAsync().
However, in the following scenario:
UI Thread cannot process that instruction because it's blocked. We have a deadlock because DoCurlAsync will never finish. ConfigureAwait(false) configures the task so that continuation after the await does not have to be run in the caller context, therefore avoiding any possible deadlocks.