Replacing multiple ConfigureAwait with the SynchronizationContextRemover

First off this is a post based upon the post An alternative to ConfigureAwait(false) everywhere.

This is a neat solution using the knowledge of how async/await and continuations fit together.

The problem Ben Willi is solving is the situation where, as a writer of library code one generally wants to use ConfigureAwat(false) so that the library does not block the synchronization context that’s used by the application. Obviously this is of particular concern when the current synchronzation context is the UI thread.

The issue is that if you have many await’s each having to set the async method/task as ConfigureAwait(false) things get a little messy and require any additional changes to also ConfigureAwait(false). Plus the synchronization onto the calling thread each time mightcause lag in the application.

See Ben’s post for a full description of the problem that’s being solved as he has example code to demonstrate the pitfalls.

What I liked and it’s a reminder of how the async/await works. What we’re trying to do is remove ConfigureAwait from all the following sort of code

await RunAsync().ConfigureAwait(false);
await ProcessAsync().ConfigureAwait(false);
await CompleteAsync().ConfigureAwait(false);

If we ignore the ConfigureAwait for a moment, in pseudo code, these three calls end up like this via the compiler

RunAsync.OnComplete
(
   ProcessAsync().OnComplete
   (
      CompleteAsync().OnComplete
      (
      )
   )
)

What happens internally is that each call is wrapped in a continuation of the previous awaited call.

See also Creating awaitable types for a look at creating awaitable types.

Let’s first look at what the new code might look like, then we’ll look at how the solution works

await new SynchronizationContextRemover();

await RunAsync();
await ProcessAsync();
await CompleteAsync();

here’s the code has for the SynchronizationContextRemover

public struct SynchronizationContextRemover : INotifyCompletion
{
   public bool IsCompleted => SynchronizationContext.Current == null;

   public void OnCompleted(Action continuation)
   {
      var prev = SynchronizationContext.Current;
      try
      {
         SynchronizationContext.SetSynchronizationContext(null);
         continuation();
      }
      finally
      {
         SynchronizationContext.SetSynchronizationContext(prev);
      }
   }

   public SynchronizationContextRemover GetAwaiter()
   {
      return this;
   }

   public void GetResult()
   {            
   }
}

What the SynchronizationContextRemover does is create an awaitable type (the GetAwaiter) which is this, so an instance of itself, which then supplies the code for an awaitable type, such as IsCompleted, GetResult and OnCompleted property and methods.

With the knowledge of how the awaits turn into methods with continuations to the next method etc. this would now form the following pseudo code

SynchronizationContextRemover.OnComplete
(
   RunAsync.OnComplete
   (
      ProcessAsync().OnComplete
      (
         CompleteAsync().OnComplete
         (
         )
      )
   )
)

Hence each awaitable method is nested, ultimately, in the OnCompleted method of SynchronizationContextRemover.

The SynchronizationContextRemover OnCompleted method then switched the synchronization context to null prior to the continuations being called so that the other method calls will now also use the null synchronization context and therefore code is not marshalled back to the original synchronization context (i.e. the UI thread).