在所有库代码中禁用捕获上下文,ConfigureAwait(false)
disable capturing context in all library code, ConfigureAwait(false)
当使用 await
时,默认情况下会捕获 SynchronizationContext
(如果存在)并使用该上下文执行 await
(延续块)之后的代码块(结果在线程上下文切换中)。
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-thread,但它似乎没有意义,因为大多数其他场景的默认值。对于内部库代码肯定没有意义,因为
在我看来,Microsoft 决定使用错误的默认设置。
现在我的问题是:
除了将我代码中的所有 await
调用与 .ConfigureAwait(false)
混淆之外,还有其他(最好是更好的)方法来解决这个问题吗?这很容易忘记,并且会降低代码的可读性。
更新:
在每个方法的开头调用 await Task.Yield().ConfigureAwait(false);
是否就足够了?如果这可以保证我将在没有 SynchronizationContext
aferwards 的线程上,则所有后续 await
调用都不会捕获任何上下文。
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.
不是真的。您无法打开任何 "out of the box" 开关来更改该行为。 ConfigureAwaiter Checker ReSharper 扩展可以提供帮助。另一种选择是使用您自己的自定义扩展方法或 SynchronizationContext
包装器来切换上下文,或者甚至是自定义等待程序。
首先,await Task.Yield().ConfigureAwait(false)
不会起作用,因为 Yield
没有 return 和 Task
。还有其他跳转到池线程的方法,但也不推荐使用它们,检查 "Why was “SwitchTo” removed from Async CTP / Release?"
如果你仍然想这样做,这里有一个很好的技巧,利用了 ConfigureAwait(false)
pushes the continuation to a pool thread 如果原始线程上有同步上下文,即使有这里没有异步:
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
// ...
}
这将使用 Task.Run
覆盖,它接受 Func<Task>
lambda,运行 它在池线程上,return 一个未包装的任务。您只需为 DoSomethingAsync
中的第一个 await
执行此操作。潜在成本与 SwitchAsync
相同:一个额外的线程切换,但代码可读性更强且结构更好。这是我在工作中使用的方法。
当使用 await
时,默认情况下会捕获 SynchronizationContext
(如果存在)并使用该上下文执行 await
(延续块)之后的代码块(结果在线程上下文切换中)。
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-thread,但它似乎没有意义,因为大多数其他场景的默认值。对于内部库代码肯定没有意义,因为
在我看来,Microsoft 决定使用错误的默认设置。
现在我的问题是:
除了将我代码中的所有 await
调用与 .ConfigureAwait(false)
混淆之外,还有其他(最好是更好的)方法来解决这个问题吗?这很容易忘记,并且会降低代码的可读性。
更新:
在每个方法的开头调用 await Task.Yield().ConfigureAwait(false);
是否就足够了?如果这可以保证我将在没有 SynchronizationContext
aferwards 的线程上,则所有后续 await
调用都不会捕获任何上下文。
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.
不是真的。您无法打开任何 "out of the box" 开关来更改该行为。 ConfigureAwaiter Checker ReSharper 扩展可以提供帮助。另一种选择是使用您自己的自定义扩展方法或 SynchronizationContext
包装器来切换上下文,或者甚至是自定义等待程序。
首先,await Task.Yield().ConfigureAwait(false)
不会起作用,因为 Yield
没有 return 和 Task
。还有其他跳转到池线程的方法,但也不推荐使用它们,检查 "Why was “SwitchTo” removed from Async CTP / Release?"
如果你仍然想这样做,这里有一个很好的技巧,利用了 ConfigureAwait(false)
pushes the continuation to a pool thread 如果原始线程上有同步上下文,即使有这里没有异步:
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
// ...
}
这将使用 Task.Run
覆盖,它接受 Func<Task>
lambda,运行 它在池线程上,return 一个未包装的任务。您只需为 DoSomethingAsync
中的第一个 await
执行此操作。潜在成本与 SwitchAsync
相同:一个额外的线程切换,但代码可读性更强且结构更好。这是我在工作中使用的方法。