ASP.NET - Synchronization Context & Configure Await
It's all about context
The async/await pattern in C# is syntactic sugar at its most decadent, allowing developers to easily write asynchronous code. However, decadence has limits, and like a too thick layer of butter cream on my post workout cupcake, Task.ConfigureAwait(bool) can leave an unpleasurable mouth feel when used incorrectly.
Naming things is hard, they should be descriptive, tell you something about their owner. So what does ConfigureAwait
tell us? Well, it obviously configures the awaiter of a task to either,
- continue on the same SynchronizationContext as the caller, if
true
is passed; or - throw caution to the wind and continue execution on whatever thread is available, if
false
By default tasks are configured to capture the synchronization context and continue on the same thread as the caller. This makes sense when writing GUI applications and you want to modify the UI after getting a result from an asynchronous operation. So then, why is it recommended to ConfigureAwait(false)
when writing libraries? Well, if the consumer of your awesome async library decides to do a synchronous blocking call (e.g. Task.Result
, Task.Wait()
or Task.GetAwaiter().GetResult()
) then the code will deadlock as the caller thread is blocked waiting for the task to finish, and the task is waiting to resume on the calling thread.
To avoid this deadlock there are two options,
- Library async methods should use
ConfigureAwait(false)
- Use async/await all the way down (no dirty calls to
Task.Result
)
Personally I wish that configure await was false by default, requiring developers to opt-in to context capturing. At some point it all starts to sound like a bit of technobable,
“Captain! Neglecting to configure the awaiter could cause a tachyon inversion of the context synchronometer, resulting in a temporal anomaly! Simply put Sir, we’ll be frozen in time!”
“Engineering, set the dilithium crystals to configure await false! Maximum warp! Engage!”
That was quite a bit of preamble to get to the point of this whole thing, ASP.NET and its SynchronizationContext
. For ASP.NET Classic (think .NET Framework), there is a System.Web.AspNetSynchronizationContext
, however, for the new and shiny ASP.NET Core there isn’t one. If you inspect SynchronizationContext.Current
you’ll find that it’s set to null
.
The AspNetSynchronizationContext
ensured things like HttpContext.Current
, user identity and culture were preserved, but in the dependency injected world of ASP.NET Core there just isn’t a need for it anymore. As an added benefit there is a performance gain when avoiding context capturing. So, for ASP.NET Core applications ConfigureAwait(false)
is not required, however, it should still be used in a library that could be called from an environment where there is a context.
Now, the question that inspired me to add this post into the ether,
If an ASP.NET Core application is running under full framework (e.g. to support Entity Framework 6), do I still need to use
ConfigureAwait(false)
?
Simple answer… nope. The SynchronizationContext
will still be null
as it’s not set by the runtime. If you don’t believe me here’s a link to my GitHub repo with proof and a comment by Stephen Cleary.