Why do i need to use ConfigureAwait(false) in all of transitive closure?

As my knowledge when we using ConfigureAwait(false) the rest of async method will be run in the thread pool.

Close, but there is an important caveat you are missing. When you resume after awaiting a task with ConfigureAwait(false), you will resume on an arbitrary thread. Take note of the words “when you resume.”

Let me show you something:

public async Task<string> GetValueAsync()
{
    return "Cached Value";
}

public async Task Example1()
{
    await this.GetValueAsync().ConfigureAwait(false);
}

Consider the await in Example1. Although you are awaiting an async method, that method does not actually perform any asynchronous work. If an async method doesn’t await anything, it executes synchronously, and the awaiter never resumes because it never suspended in the first place. As this example shows, calls to ConfigureAwait(false) may be superfluous: they may have no effect at all. In this example, whatever context you were on when you enter Example1 is the context you will be on after the await.

Not quite what you expected, right? And yet, it’s not altogether unusual. Many async methods may contain fast paths that don’t require the caller to suspend. The availability of a cached resource is a good example (thanks, @jakub-dÄ…bek!), but there plenty of other reasons an async method might bail early. We often check for various conditions at the beginning of a method to see if we can avoid doing unnecessary work, and async methods are no different.

Let’s look at another example, this time from a WPF application:

async Task DoSomethingBenignAsync()
{
    await Task.Yield();
}

Task DoSomethingUnexpectedAsync()
{
    var tcs = new TaskCompletionSource<string>();
    Dispatcher.BeginInvoke(Action(() => tcs.SetResult("Done!")));
    return tcs.Task;
}

async Task Example2()
{
    await DoSomethingBenignAsync().ConfigureAwait(false);
    await DoSomethingUnexpectedAsync();
}

Take a look at Example2. The first method we await always runs asynchronously. By the time we hit the second await, we know we’re running on a thread pool thread, so there’s no need for ConfigureAwait(false) on the second call, right? Wrong. Despite having Async in the name and returning a Task, our second method wasn’t written using async and await. Instead, it performs its own scheduling and uses a TaskCompletionSource to communicate the result. When you resume from your await, you might[1] end up running on whatever thread provided the result, which in this case is WPF’s dispatcher thread. Whoops.

The key takeaway here is that you often don’t know exactly what an ‘awaitable’ method does. With or without ConfigureAwait, you might end up running somewhere unexpected. This can happen at any level of an async call stack, so the surest way to avoid inadvertently taking ownership of a single-threaded context is to use ConfigureAwait(false) with every await, i.e., throughout the transitive closure.

Of course, there may be times when you want to resume on your current context, and that’s fine. That is ostensibly why it’s the default behavior. But if you don’t genuinely need it, then I recommend using ConfigureAwait(false) by default. This is especially true for library code. Library code can get called from anywhere, so it’s best adhere to the principle of least surprise. That means not locking other threads out of your caller’s context when you don’t need it. Even if you use ConfigureAwait(false) everywhere in your library code, your caller will still have the option to resume on their original context if that’s what they want.

[1] This behavior may vary by framework and compiler version.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)