Skip to content

Synchronization Context and Configure Await in CSharp

Posted on:August 20, 2023 at 08:23 PM

csharp-sync-context-conf-await Photo by v2osk Unsplash

Table of Contents

Open Table of Contents

SynchronizationContext

SynchronizationContext is a representation of the current environment that our code is running. For example, in an asynchronous program, if we execute a unit of work in a different thread, we capture the current environment and put it into the SynchronizationContext, and hold it in the Task object.

Different frameworks have different communication protocols between threads. All threads have their own SynchronizationContext so if we delegate a job to another thread, the current SynchronizationContext's snapshot is taken and given to a new thread.

In the simplest form, SynchronizationContext shows where code may work. The base class SynchronizationContext exposes multiple virtual methods. One of them is the Post takes delegate as a parameter. In the default implementation of the SynchronizationContext, the Post function sends the delegate parameter to the Thread Pool using QueueUserWorkItem. Frameworks inherit this base class and override the defaults:

Why does SynchronizationContext matter?

When we wait for a Task with await:

  1. If the SynchronizationContext.Current is not null, this value is the captured SynchronizationContext.
  2. If it is null, synchronization context is the current TaskScheduler (TaskScheduler.Default is the context of the thread pool)

Wrong use of SynchronizationContext with ConfigureAwait may result in deadlocks!


ConfigureAwait

ConfigureAwait can be used with any Task or Task<T> object. ConfigureAwait specifies, how to behave when await is used inside the Task or Task<T> object.

ConfigureAwait takes a single parameter named continueOnCapturedContext with default value true. If false is given to this parameter, after awaiting is finished in the Task, Task may continue with different SynchronizationContext. For example when a user clicks the button in the WPF application… (in this situation the SynchronizationContext is aDispatcherSynchronizationContext)

public async void Button1_Click(){
	var taskResult = await SomeTaskTodo().ConfigureAwait(false);
	// ERROR: We cannot do this because context is not captured. We
    // do not know what the  textBox1 is due to the  context is not
    // captured and we may be in different thread in thread pool
    textBox1.Text = taskResult;
}

Deadlocks

ConfigureAwait may be required for avoiding deadlocks. If the captured context is blocked by another operation that waits for a task to finish, a deadlock will occur. For example, if the UI thread synchronously waits for a task to finish deadlock will happen.

public static async Task<string> SomeWork(Uri uri)
{
  using (var client = new HttpClient())
  {
	// await is used here without ConfigureAwait(false) context will be
    // captured but it can't because context has blocked synchronously
    // Button1_Click function so DEADLOCK!
    var s = await client.GetStringAsync(uri);
	return s
  }
}

public void Button1_Click(...)
{
  // Waiting the result synchronously and the context is captured
  // ConfigureAwait(false) is not used
  var resultTask = SomeWork(...);
  textBox1.Text = resultTask.Result;
}

For the solution, libraries should be independent of context and should use ConfigureAwait(false) as much as possible. As in the code above, we should not block asynchronous code synchronously.

Solution 1

var s = await client.GetStringAsync(uri).ConfigureAwait(false);

Solution 2

public void Button1_Click(...)
{
	var result = await SomeWork(...);
	textBox1.Text = result;
}

If we have work that depends on the context, we can’t use ConfigureAwait(false)

Some think to consider, ConfigureAwait is not block-scoped:

// Don't capture and resume on any available thread
await DoSomethingAsync().ConfigureAwait(false);

// Capture and resume on whatever context was current at this point
await DoSomethingElseAsync();

// Don't capture and resume on any available thread
await DoAnotherThingAsync().ConfigureAwait(false);

References