Search code examples
c#async-awaitconfigureawait

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.


Solution

  • 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.