An alternative to ConfigureAwait(false) everywhere


One of the general recommendations you may often read is to use ConfigureAwait(false) in library code. This is so that when the library is used, it does not block the synchronization context in use by the application (e.g. the UI thread). If the library doesn’t know anything about the app, it doesn’t depend on the application’s context and doesn’t need to run within it. This makes sense but it ends up truly meaning that you have to put ConfigureAwait(false) on every async call in your entire library. To me, that seems …excessive.

As I see it, If you are using ConfigureAwait(), you probably really care about it and want it to actually work. Unfortunately, because async methods might actually complete synchronously, the call to ConfigureAwait() might not affect anything. That means you have to put it on the next async call too, and so on, until it is on every single method in your library.

Tagging every single call with a decorator method has always bothered me as inelegant and it clutters my code. It’s also something that can easily be forgotten when updating or adding new code. But there is also another more hidden potential issue – the first async call tree. The first time you attempt to await an async method, it doesn’t get the benefit of ConfigureAwait(false). Look at this code

clip_image002

The first call to FindWorkAsync() has to run before we get a Task to configure. That means that everything that happens inside the first call happens on our caller’s context. That means if any long running actions happen in FindWorkAsync() or in its children, it may affect the callers context.

This behavior can be seen by running this sample WPF application

clip_image004

The number on this form is updated by a DispatchTimer every 100ms. DispatchTimers run on the UI thread. If anything blocks the UI thread, you will see that timer pause. In addition, all the buttons are disabled while the work is being done so there is a good visual indicator of when the work is finally finished (the buttons become enabled again).

Using this app, lets look at a couple of scenarios and see how the it behaves…

Regular async/await

Run Case 0” runs a regular set of async/await calls without the use of ConfigureAwait(). This is the basic async/await code as it is usually implemented.

clip_image006

clip_image008

clip_image010

The whole operation takes a few seconds. While this is running, the UI periodically freezes because WorkWithoutCA() calls BlockingWorkAsync() which calls Thread.Sleep(1000). During this sleep time, the timer doesn’t change. The behavior looks like this:

  1. Buttons disabled
  2. UI freezes for 1 second periodically when BlockingWorkAsync() blocks synchronously on Thread.Sleep
  3. Buttons enabled

Shallow coverage with ConfigureAwait(false)

Run Case 1” runs the same sequence of async/await calls, except that some calls to ConfigureAwait(false) have been added to the entry of the library’s API. The ConfigureAwait(false) calls are only at the top level of our process. The lower level methods DoWorkAsync() and BlockingWorkAsync() are unchanged.

clip_image012

clip_image014

When this runs, you will notice that the UI freezes for a short time just once. That is the first call to DoWorkAsync() which is still running on the UI thread. The 2nd and 3rd calls to DoWorkAsync() are not on the UI thread and so don’t freeze the UI

  1. Buttons disabled
  2. UI freezes once for 1 second during the first DoWorkAsync() call
  3. UI unfrozen for subsequent calls to DoWorkAsync()
  4. Buttons enabled

It’s this 2nd step that I’m concerned with in this discussion. Obviously the overall behavior is much better because in this case the UI is unfrozen for a majority of the time. But if we are concerned about this at all, why aren’t we concerned about eliminating it completely? I want to push that behavior across all of my library’s functionality, not just stuff that happens after the first await.

Full coverage with ConfigureAwait(false)

Run Case 2” solves this in the way that is typically recommended. You push ConfigureAwait() all the way down the call tree.

clip_image016

clip_image018

clip_image020

The behavior is now as we desire it to be – no blocking the UI during any of the calls.

  1. Buttons disabled
  2. UI unfrozen for all calls to DoWorkAsync()
  3. Buttons enabled

The main problem with this approach is that now we have a maintenance issue. Every new await needs to have ConfigureAwait(false) put on it – just in case. It also tends to clutter the code since this one thing is now everywhere. As a matter of style it makes the code less readable.

So I am proposing an alternative…

Going manual without ConfigureAwait(false)

Run Case 3

What if we did what ConfigureAwait() does for us, but we do it once at the beginning of our callstack and didn’t have to think about it again? Unfortunately, that means not using a fluent API. The fluent API of ConfigureAwait() is the reason for the “first call” problem. The snippet of code below does not use ConfigureAwait(), but uses a helper object to temporarily remove the SynchronizationContext manually (and restores it before returning to the caller).

clip_image022

clip_image024

clip_image026

This gives us the same behavior as Case 2 but the code is cleaner. With this technique, I no longer have to remember to put ConfigureAwait(false) all over my code which makes it easier to maintain and to read. The only thing I need to do is insert a single await on the helper object at the beginning of my public APIs and all its children will get called on the default context automatically with no changes to them at all.

Additional notes

  • The sample is available on my github repo. That code uses some extra methods which I’ve removed in the snippets above. Those methods just print some diagnostic info out so that you can look at the debug output and see the context flowing or not existing at all the various points in the app. It also contains a Case 4 which uses Task.Run to invoke the method instead of the Case 3 wrapper. These two cases are very similar but when Task.Run is used, the method gets queued to the thread pool and will start at some later point in time. Case 3 will initiate the asynchronous method immediately.
  • I am not advocating never using ConfigAwait(false). I’ve had very good success using it in specific targeted scenarios when we knew where the problem was and where to put ConfigureAwait(false) to resolve that problem. I am suggesting that it’s not a cure all and that blanketed use of ConfigAwait is not a good practice.
  • I’m also open to other suggestions. After discussing this with others and looking at a few different options (one of them is in the Github history of case 3), this seems the most reasonable so far.
  • This sample is a WPF application because its easiest to visualize for demo purposes, but the concept is applicable to any .NET library with async code whether running on the client, on a server, or in the cloud.

If you have anything to add or suggest, please leave a comment below.

Comments (5)

  1. Very interesting!

    Minor improvement: Since there’s no per-instance state, you could make it a singleton and avoid a bit of garbage.

    I recently added a NoContext wrapper to my AsyncEx library: https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Tasks/SynchronizationContextSwitcher.cs#L36

    It has a similar goal, but NoContext requires the code without context to be passed as a lambda. So the code structure ends up being “the code in this block executes without context” instead of “the code after this statement executes without context”.

    Have you forgotten to push to your repo? I’m not seeing SynchronizationContextRemover there.

    1. BenWilli says:

      Yes I had forgotten to push to the repo. Its now there. My original thought was the same as yours – wrap a lambda (and is the older version in the repo), but someone suggested this way which I think is more explicit in the intent. -thanks

  2. When the original Async CTP shipped, there were a handful of “convenience await” systems that could be used to jump contexts. E.g., you could await a SynchronizationContext (or TaskScheduler) to “jump” to that thread for the remainder of the method.

    However, these were removed before release because it becomes difficult to know what context you’re in when you enter the catch/finally block. E.g.:

    try
    {
    // Code that may throw (1)
    await SynchronizationContextRemover.Instance;
    // Code that may throw (2)
    }
    catch (Exception)
    {
    // What is the current SyncCtx here?
    }

    1. BenWilli says:

      a quick test shows the context is still null until the method exits for #2. I’d argue #1 is a misuse of the technique and that this should only be used only as the beginning of a method (and outside of any try/catch block). Maybe I’ll add code analysis rule to the solution.

  3. EricNL says:

    Shouldn’t IsCompleted remain forever true once it is ever been true?

Skip to main content