Based on numerous books and blogs including this excellent one here, it is clear that when one writes a dll library exposing helper async methods i.e. the wrapper methods, it is generally considered a best practice to internally complete the I/O task of actual async methods on a threadpool thread like so (pseudo code shown below for brevity and I'm using HttpClient
as an example)
public Async Task<HttpResponseMessage> MyMethodAsync(..)
{
...
var httpClient = new HttpClient(..);
var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false);
...
return response;
}
The key here is the usage of ConfigureAwait(false)
so that IO task completion occurs on a threadpool thread instead of on the original thread context, thereby potentially preventing deadlocks.
My question is from the perspective of a caller. I'm particularly interested in a scenario where there are layers of method calls between the caller and the above method call, as the following example shows.
CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync
Is it enough to have ConfigureAwait(false)
on the final method only or should one also ensure Method1Async
and Method2Async
also internally call their async methods with ConfigureAwait(false)
?
It seems silly to have it included on all these intermediary methods, especially if Method1Async
and Method2Async
are simply overloads that end up calling MyMethodAsync
.
Any thoughts, please enlighten us!
Updated with Example So if I have a library with the following private async method,
private async Task<string> MyPrivateMethodAsync(MyClass myClass)
{
...
return await SomeObject.ReadAsStringAsync().ConfigureAwait(false);
}
should I make sure the following public overloaded methods both also include ConfigureAwait(false) as shown below?
public async Task<string> MyMethodAsync(string from)
{
return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false);
}
public async Task<string> MyMethodAsync(string from, string to)
{
return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false);
}
When the task is awaited, it creates a corresponding
TaskAwaiter
to keep track of the task which also captures the currentSynchronizationContext
. After the task completes, the awaiter runs the code after the await ( called the continuation) on that captured context.You can prevent that by calling
ConfigureAwait(false)
, which creates a different kind of awiatable (ConfiguredTaskAwaitable
) and its corresponding awaiter (ConfiguredTaskAwaitable.ConfiguredTaskAwaiter
) that does not run the continuation on the captured context.The point is that for each
await
, a different instance of an awaiter is created, it is not something that is shared between all the awaitables in the method or program. So it's best that you callConfigureAwait(false)
for eachawait
statement.You can see the source code for the awaiters here.
Definitely not.
ConfigureAwait
just as it's name suggest configures theawait
. It only affects theawait
coupled with it.ConfigureAwait
actually returns a different awaitable type,ConfiguredTaskAwaitable
instead ofTask
which in turn returns a different awaiter typeConfiguredTaskAwaiter
instead ofTaskAwaiter
If you want to disregard the
SynchronizationContext
for all yourawait
s you must useConfigureAwait(false)
for each of them.If you want to limit the use of
ConfigureAwait(false)
you can use myNoSynchronizationContextScope
(see here) at the very top: