考虑这个例子:
async Task Foo()
{
button.Text = "This is the UI context!";
await BarA();
button.Text = "This is still the UI context!";
await BarB();
button.Text = "Oh no!"; // exception (?)
}
async Task BarA()
{
await Calculations();
}
async Task BarB()
{
await Calculations().ConfigureAwait(false);
}
如何在不读取await BarB()
函数体的情况下调用async Task BarB()
来改变上下文?我是否真的需要确切知道异步函数是否在任何时候调用ConfigureAwait(false)
?或者这个例子可能是错误的并且没有例外?
BarB()
所做的事情在很大程度上是无关紧要的,除非BarB()
需要与UI交谈。这里的重要部分是await
:
await BarB();
它是await
捕获上下文(也是) - 如果它发现自己从错误的上下文继续,则协商回到正确的上下文。所以;除非你写:
await BarB().ConfigureAwait(false);
你应该没事。
如果我们假设ConfigureAwait(false)
不需要直接更新UI或执行任何其他上下文绑定操作,那么BarB()
中的BarB()
看起来很正常并且可能是正确的。
补充Marc's answer - 请记住,async
是一种方法的实现细节。如何或为什么一个方法创建一个Task
,它回馈给你的很大程度上是透明的(这就是为什么async
不是签名的一部分,不允许在接口定义中等)
虽然你的方法确实与它调用的方法共享它的“当前上下文”,但只有那些方法,如果他们想知道当前上下文,才会调用SynchronizationContext.Current
。然后,他们将使用该上下文中的方法在必要时“重新开始”。对上下文的任何“更改”通常实际上是通过将continuation切换到已经具有正确上下文集的合适线程来执行的。
这些方法通常不会做的是调用SetSynchronizationContext
。只有当一个方法确实调用了你必须担心“你的”上下文的变化时。 (如果某些基础结构代码的同步上下文是自由线程但需要其他环境资源,则可能会调用该方法。它们将获得一个线程池线程,调用SetSynchronizationContext
,执行continuation,然后再次取消设置同步上下文)。