禁用所有库代码中的捕获上下文,ConfigureAwait(false)
在使用await
,默认情况下会捕获SynchronizationContext
(如果存在),并使用该上下文执行await
(continuation blocks)之后的代码块(这会导致线程上下文切换)。
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)
}
虽然这个默认值在你想在异步操作完成后回到UI线程的UI上下文中是有意义的,但作为大多数其他场景的默认值,这似乎没有任何意义。 对于内部库代码来说,这当然没有意义,因为
在我看来,微软已经决定了错误的默认设置。
现在我的问题:
是否有任何其他(最好是更好的)方法来解决这个问题,而不是使用.ConfigureAwait(false)
在我的代码中混淆所有await
调用? 这很容易被遗忘,并使得代码的可读性降低。
更新:可能只需要调用await Task.Yield().ConfigureAwait(false);
在每个方法的开始? 如果这可以保证我将在没有SynchronizationContext
的线程上继续执行,则所有后续的await
调用都不会捕获任何上下文。
首先, await Task.Yield().ConfigureAwait(false)
将不起作用,因为Yield
不返回Task
。 还有其他跳转到池线程的方式,但不建议使用它们,请选中“为什么”从“Async CTP / Release”中删除“SwitchTo”?
如果你仍然想这样做,这里有一个很好的诀窍,利用了如果在原始线程上有同步上下文, ConfigureAwait(false)
会将继续推送到池线程的事实,即使这里没有异步:
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
// ...
}
再次,这不是我广泛使用自己的东西。 我对使用ConfigureAwait(false)
有类似的担忧。 一个结果是,虽然ConfigureAwait(false)
可能不是普遍完美的,使用它与await
,只要你不关心同步上下文是要走的路。 这是.NET源代码本身密切关注的指导原则。
另一个结果是,如果您担心DoSomethingElseAsync
中可能没有正确使用ConfigureAwait(false)
第三方代码,只需执行以下操作:
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
// ...
}
这将使用接受Func<Task>
lambda的Task.Run
覆盖,在池线程上运行它并返回一个解包的任务。 你只会在DoSomethingAsync
里面第一次await
。 潜在成本与SwitchAsync
相同:一个额外的线程切换,但代码更易读并且结构更好。 这是我在工作中使用的方法。
有没有更好的方法来解决这个问题,比在我的代码中用.ConfigureAwait(false)混淆所有等待的调用? 这很容易被遗忘,并使得代码的可读性降低。
不是真的。 没有任何“开箱即用”开关,您可以打开以改变该行为。 ConfigureAwaiter Checker ReSharper扩展可以提供帮助。 另一种方法是将您自己的自定义扩展方法或用于切换上下文的SynchronizationContext
包装器,或者甚至是自定义的awaiter。
上一篇: disable capturing context in all library code, ConfigureAwait(false)