Álvaro García
Álvaro García

Reputation: 19356

Should I use configure await in all methods or only in the first method?

I have a library with async methods and I have read that for libraries, it is recommended to use ConfigureAwait(false).

For example, if I have something like:

public async Task myMethod01()
{
    await myMethod02();
}

private async Task myMethod02()
{
    await myMethod03();
}

private async Task myMethod03()
{
    await anotherMetodAsync().ConfigureAwait(false);
}

The library user can only use method01, the other 2 methods are private because they are auxliar methods for the main method method01().

But I don't know if it is needed to use ConfigureAwait only in the first method of the chain call or I should to use in all of them.

Upvotes: 0

Views: 2175

Answers (2)

FlashOver
FlashOver

Reputation: 2073

tl;dr

Yes, it is needed in order to ensure that all of your asynchronous continuations within your library code are executed on a thread pool thread (depending on the SynchronizationContext/TaskScheduler in use).

Would you like to know more?

Task.ConfigureAwait(Boolean)

  • true attempts to marshal the remainder of the async method back to the original context captured
  • false schedules the remainder of the async method on a thread pool thread

Consider the following WPF example: WPF uses the DispatcherSynchronizationContext to resume asynchronous continuations on the UI context, because a background thread cannot update the contents of Controls.

private async void Button_Click(object sender, RoutedEventArgs e)
{
    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await CompleteAsynchronously();

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await CompleteAsynchronously().ConfigureAwait(false);

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //true
}

private async Task CompleteAsynchronously()
{
    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //true
}

Here, you see that the continueOnCapturedContext flag of the called method has no effect on the caller. Yet, the called method runs (or at least starts to run) on the thread that the caller was running on, of course.

However, the capturing of the current context (either the current SynchronizationContext; if null then the current TaskScheduler) only occurs when an incomplete Task is awaited. If the Task completes synchronously, continueOnCapturedContext has no effect and the remainder of the method continues to run synchronously on the current thread.

private async void Button_Click(object sender, RoutedEventArgs e)
{
    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context

    await CompleteSynchronously().ConfigureAwait(false);

    logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
}

private async Task CompleteSynchronously()
{
    await Task.Delay(0);
}

So, in your library code (assuming you never require con­text), you should always use ConfigureAwait(false) in order to ensure that no context is captured for asynchronous continuations, regardless of the framework calling into your assemblies (e.g. WPF, ASP.NET Core, Console, ...).

For more details, have a look at Best Practices in Asynchronous Programming (i.a. ConfigureAwait) in this MSDN Magazine Article by Stephen Cleary.

Upvotes: 2

Marco Luzzara
Marco Luzzara

Reputation: 6046

You should use the ConfigureAwait(false) on all the async calls. When this is not done, the first async call (without the ConfigureAwait(false)) will take the SynchronizationContext and this could cause a deadlock in certain conditions (like on ASP.NET) when you wait synchronously on that call.

My advice is to read this article written by Stephen Cleary. The part you are interested in is:

Using ConfigureAwait(false) to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false) for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(false) to avoid deadlock is at best just a hack).

Upvotes: 2

Related Questions