When working with C#’s async/await keywords, you need to know about the Synchronization Context. I did not understand it at first and it caused many headaches (or rather, migraines).
But before we talk about Synchronization Contexts, let’s lay some groundwork: DeadLocks and ConfigureAwait. Deadlocks
Sometimes you need to block and wait for the result of an asynchronous operation.
Ideally you should just use async code all-the-way, and not block, but there are times where it is necessary (I may explore the reasons in a future blog post).
If you choose block an async call, remember that blocking can be dangerous. Let’s examine a simple piece of Xamarin iOS code, notice the following:
- We have a button “click” event handler (a “touch” event in iOS).
- Inside the event handler we wait for the response from the GetDataAsync method by calling
- We do a simple
awaitin the async method.
What you may not expect is that when the user taps on the Button, the code will hang indefinitely. This happens because when
GetDataAsync().Result is called, the code sits and waits for a result, and it does so on the UI thread. While it does so, await
ServiceLayer.FetchDataAsync() is called.
It will execute, and then the code will try to resume on the same context (namely the UI thread), but the context is already blocked by the
GetDataAsync().Result call from before. Catch-22. Deadlock. Everybody is waiting on everyone else.
There is a solution. Changing the
GetDataAsync method as follows solves the problem:
ConfigureAwait(false) tells our async code that when method execution continues after the await, that it does not have to do so on the same synchronization context as before, meaning it can execute on another thread, complete and return control to the UI thread. No more deadlock! If you are writing library code, it’s a good idea to use ConfigureAwait(false), because you don’t know if callers of your library may cause deadlocks.
The Synchronization Context
So what exactly is a ‘Synchronization Context’ and how does it relate to a Thread? You can have a look at if you are curious. Set a breakpoint in some of your own asynchronous code and add a watch for:
If the watch value is
null, it means there is no synchronization context on the current thread. However, sometimes it has a value because your code needs a synchronization context.
This is the case with most “UI” code like WPF and Xamarin iOS (and even ASP.NET). Most UI frameworks have a limitation in place where only one change to the UI is allowed at a time. A single UI thread is in charge of that. If 10 changes need to be made, then 10 changes need to be queued up and executed in sequence.
The limitation is in place to help the UI framework to avoid the nightmare of managing multiple threads updating the user interface at the same time, putting it in an inconsistent state. The synchronization context, if present, manages communication with the UI thread in an orderly manner.
(Although, a synchronization context can manage communication with other things too, for example the ASP.NET request context.) Remember that the CLR does not know about async/await. Await is simply a language feature found in C# and Visual Basic. When code gets compiled to MSIL, the async/await keywords are no longer there.
Scott Chamberlain has a great answer on StackOverflow that illustrates roughly what the compiled MSIL does and how execution proceeds based on whether or not there is a synchronization context.
Notice the if statement at the bottom that either posts to the synchronization context or runs the code normally in a Task:
An extension method
At NML we prefer to always state explicitly how we want the task continuation to occur. So even though a Task’s default is ConfigureAwait(true), we still specify it as such so that we are always cognizant of what’s happening “under the hood”.
However, when you look at a lot of code, some with ConfigureAwait(true) and some with ConfigureAwait(false), it’s not easy to spot where they differ. So we use either ConfigureAwait(false), or a useful extension method, ContinueOnCapturedContext(). It does the same thing, but just differentiates it from ConfigureAwait(false) in a more visual manner. The extension method is simple:
- When execution continues after an await, generated code ensures that the code runs on the same synchronization context that it did before (by default).
- The synchronization context’s job is simply to coordinate communication with a resource (UI thread, request context, etc).
- Use ConfigureAwait(false) to tell async code that it does not have to continue on the same context. It’s important to use this in library code to prevent deadlocks.