disable capturing context in all library code, ConfigureAwait(false)
When using await
, by default the SynchronizationContext
(if one exists) is captured and the codeblocks after the await
(continuation blocks) is executed using that context (which results in thread context switches).
public async Task DoSomethingAsync()
{
// We are on a thread that has a SynchronizationContext here.
await DoSomethingElseAsync();
// We are back on the same thread as before here
//(well sometimes, depending on how the captured SynchronizationContext is implemented)
}
While this default might make sense in the context of the UI where you want to be back on the UI-thread after an asynchronous operation completes, it doesn't seem make sense as the default for most other scenarios. It certainly doesn't make sense for internal library code, because
It seems to me that Microsoft have decided for the wrong default.
Now my question:
Is there any other (preferably better) way to solve this than by cluttering all await
calls in my code with .ConfigureAwait(false)
? This is just so easy to forget and makes the code less readable.
Update: Would it maybe suffice to call await Task.Yield().ConfigureAwait(false);
at the beginning of each method? If this would guarantee me that I will be on a thread without a SynchronizationContext
aferwards, all subsequent await
calls would not capture any context.
To begin with, await Task.Yield().ConfigureAwait(false)
won't work because Yield
doesn't return a Task
. There are other ways of hopping over to a pool thread, but their use isn't recommended either, check "Why was “SwitchTo” removed from Async CTP / Release?"
If you still want to do it, here's a nice trick exploiting the fact that ConfigureAwait(false)
pushes the continuation to a pool thread if there is a synchronization context on the original thread, even though there is no asynchrony here:
static Task SwitchAsync()
{
if (SynchronizationContext.Current == null)
return Task.FromResult(false); // optimize
var tcs = new TaskCompletionSource<bool>();
Func<Task> yield = async () =>
await tcs.Task.ConfigureAwait(false);
var task = yield();
tcs.SetResult(false);
return task;
}
// ...
public async Task DoSomethingAsync()
{
// We are on a thread that has a SynchronizationContext here.
await SwitchAsync().ConfigureAwait(false);
// We're on a thread pool thread without a SynchronizationContext
await DoSomethingElseAsync(); // no need for ConfigureAwait(false) here
// ...
}
Again, this is not something I'd extensively use myself. I've had similar concerns about using ConfigureAwait(false)
. One outcome was, while ConfigureAwait(false)
might not be universally perfect, using it with await
as soon as you don't care about synchronization context is the way to go. This is the guideline the .NET source code itself follows closely.
Another outcome was, if you're concerned about a 3rd party code inside DoSomethingElseAsync
which may not be using ConfigureAwait(false)
correctly, just do the following:
public async Task DoSomethingAsync()
{
// We are on a thread that has a SynchronizationContext here.
await Task.Run(() => DoSomethingElseAsync()).ConfigureAwait(false);
// We're on a thread pool thread without a SynchronizationContext
await DoYetSomethingElseAsync(); // no need for ConfigureAwait(false) here
// ...
}
This will use the Task.Run
override which accepts a Func<Task>
lambda, run it on a pool thread and return an unwrapped task. You'd do it only for the first await
inside DoSomethingAsync
. The potential cost is the same as for SwitchAsync
: one extra thread switch, but the code is more readable and better structured. This is the approach I use in my work.
Is there a better way to solve this than by cluttering all await calls in my code with .ConfigureAwait(false)? This is just so easy to forget and makes the code less readable.
Not really. There isn't any "out of the box" switch that you can turn on to change that behavior. There is the ConfigureAwaiter Checker ReSharper extension which can help. The other alternative would be to roll you own custom extension method or SynchronizationContext
wrapper which switches the context, or alternatively, even a custom awaiter.
上一篇: 通过AJAX发送对象数组