Reputation: 19356
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
Reputation: 2073
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.
Upvotes: 2
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 useConfigureAwait(false)
for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. UsingConfigureAwait(false)
to avoid deadlock is at best just a hack).
Upvotes: 2