When should I use ConfigureAwait(true)?
Has anyone come across a scenario for using ConfigureAwait(true)
? Since true
is the default option I cannot see when would you ever use it.
Solution 1:
true to attempt to marshal the continuation back to the original context captured; otherwise, false.
It's actually more like saying that ConfigureAwait(true)
is like using .ContinueWith( t => {...}, TaskScheduler.FromCurrentSynchronizationContext())
, where ConfigureAwait(false)
is like using .ContinueWith( t => {...})
. If you pass false, then the continuation is being allowed to run on a thread-pool thread instead of pulling back to the current synchronization context.
Solution 2:
One possibility I can see is if you're writing code in a library and you want to allow your callers to decide whether it's appropriate to continue on the original context1 (although I'd usually argue for never continuing on the original context from within library code)
Your caller will either pass a bool
parameter or set some configuration value, and so you will not know until runtime what the correct argument value is.
This is the general type of answer for APIs such as this that have a no-args variant and a variant with a single argument, where the no-args variant is documented as "the same as the single argument variant with well-known value x" - if you won't know until runtime what the correct value to pass is, then you should just call the single argument variant with the correct runtime value.
1 E.g. your caller is also supplying a delegate. Your caller will know (and can decide) whether that delegate needs to be back on the original context or not.
Solution 3:
Since true is the default option I cannot see when would you ever use it.
One obvious use case is when you want to make sure that every time something is awaited, there is made an explicit and deliberate choise about what to do with the synchronization context.
Example policy from http://newmedialabs.co.za/blog/post/SynchronizationContexts:
At NML we prefer to always state explicitly how we want the task continuation to occur. So even though a Task's default is ConfigureAwait(true), we still specify it as such so that we are always cognizant of what's happening "under the hood".
Although to improve readability they use an extension instead of ConfigureAwait(true)
directly:
However, when you look at a lot of code, some with ConfigureAwait(true) and some with ConfigureAwait(false), it's not easy to spot where they differ. So we use either ConfigureAwait(false), or a useful extension method, ContinueOnCapturedContext(). It does the same thing, but just differentiates it from ConfigureAwait(false) in a more visual manner.
Solution 4:
A situation to use ConfigureAwait(true)
is when performing await in a lock, or using any other context/thread specific resources. This requires a synchronization context, which you will have to create, unless you are using Windows Forms or WPF, which automatically create a UI synchronization context.
In the following code (assumed to be called from the UI thread and synchronization context), if ConfigureAwait(false)
was used, the locks would attempt to release on a different thread, causing an exception. This is a simple example that updates a large config file if it has changed from an external source, attempting to avoid write disk IO if the config file is the same as before.
Example:
/// <summary>
/// Write a new config file
/// </summary>
/// <param name="xml">Xml of the new config file</param>
/// <returns>Task</returns>
public async Task UpdateConfig(string xml)
{
// Ensure valid xml before writing the file
XmlDocument doc = new XmlDocument();
using (XmlReader xmlReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { CheckCharacters = false }))
{
doc.Load(xmlReader);
}
// ReaderWriterLock
configLock.AcquireReaderLock(Timeout.Infinite);
try
{
string text = await File.ReadAllTextAsync(ConfigFilePath).ConfigureAwait(true);
// if the file changed, update it
if (text != xml)
{
LockCookie cookie = configLock.UpgradeToWriterLock(Timeout.Infinite);
try
{
// compare again in case text was updated before write lock was acquired
if (text != xml)
{
await File.WriteAllTextAsync(ConfigFilePath, xml).ConfigureAwait(true);
}
}
finally
{
configLock.DowngradeFromWriterLock(ref cookie);
}
}
}
finally
{
configLock.ReleaseReaderLock();
}
}
Solution 5:
If you are using Azure's Durable Functions, then you must use ConfigureAwait(true)
when awaiting your Activity functions:
string capture = await context.CallActivityAsync<string>("GetCapture", captureId).ConfigureAwait(true);
Otherwise you will likely get the error:
"Multithreaded execution was detected. This can happen if the orchestrator function code awaits on a task that was not created by a DurableOrchestrationContext method. More details can be found in this article https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-checkpointing-and-replay#orchestrator-code-constraints.".
Further information can be found here.