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.
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).
true
attempts to marshal the remainder of the async method back to the original context capturedfalse
schedules the remainder of the async method on a thread pool threadConsider 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 context), 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.