ConfigureAwait on a call which is already running on a threadpool thread?

Viewed 105

I have a WPF application (and therefore a synchronization context). In my async calls on the UI thread I use ConfigureAwait(false) to continue on a background thread. That part I fully understand.

However, once on a background thread, is ConfigureAwait(false) required for all following calls?

public async void SomeMouseClick(someArgs)
{
    await OnTheBackground().ConfigureAwait(false);
}

public async Task OnTheBackground()
{
    await someClient.Execute();
    // am I on the UI thread now, or on still on a thread pool thread?
}
1 Answers

The ConfigureAwait() only affects the continuation, so your call to OnTheBackground() is called from the context of the calling thread - a new thread is NOT created at that point.

After the await for OnTheBackground() completes, the continuation MAY be on a new thread - but if nothing in OnTheBackground() blocked it may still continue on the calling thread. So you have to be careful about doing compute-bound stuff, even in an async method!

This is best illustrated with an example. Consider the following code for a WPF application with a single button called Button with its click event handled by a Button_Click() method:

bool firstTime = true;

private async void Button_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("Button_Click() called on thread " + Thread.CurrentThread.ManagedThreadId);

    await OnTheBackground().ConfigureAwait(false);

    Debug.WriteLine("Button_Click() continued on thread " + Thread.CurrentThread.ManagedThreadId);
}

public async Task OnTheBackground()
{
    Debug.WriteLine("OnTheBackground() called on thread " + Thread.CurrentThread.ManagedThreadId);

    if (firstTime)
        await Task.Delay(0);
    else
        await Task.Delay(10);

    firstTime = false;

    Debug.WriteLine("OnTheBackground() continued on thread " + Thread.CurrentThread.ManagedThreadId);
}

When you click the button the first time, it will wind up calling await Task.Delay(0); inside OnTheBackground(). Because this does not need to block, when the await Task.Delay(0) returns, it continues on the same thread that called it.

Then when this returns to Button_Click(), even though the await in there has .ConfigureAwait(false) it will continue on the original UI thread, because nothing that it called has transitioned to a different thread.

Thus, on first press of the button the output in the debug window is something like:

Button_Click() called on thread 1
OnTheBackground() called on thread 1
OnTheBackground() continued on thread 1
Button_Click() continued on thread 1

Everything is on the same thread, as expected.

However, on second press of the button it ends up calling await Task.Delay(10); which will cause the await to return on a new thread, since Task.Delay(10) will block.

So when it gets back to await OnTheBackground().ConfigureAwait(false);, it will NOT continue on the UI thread, but rather continues on a different thread.

Thus on the second press of the button, the debug output is something like:

Button_Click() called on thread 1
OnTheBackground() called on thread 1
OnTheBackground() continued on thread 1
Button_Click() continued on thread 4

In this case, you can see that the code after the await in Button_Click() is now running on a non-UI thread.

Related