如何解释await / async同步上下文切换行为

有几件事(但主要的一点)我不明白以下代码的行为。

有人可以帮忙解释一下吗?

它实际上是非常简单的代码 – 只是一个调用异步方法的常规方法。 在异步方法中,我使用using块尝试临时更改SynchronizationContext。

在代码的不同点,我探测当前的SynchronizationContext。

这是我的问题:

  1. 当执行到达位置“2.1”时,上下文已变为上下文#2。 好的。 然后,因为我们点击`await`,返回一个Task并执行跳回到位置“1.2”。 为什么那么,在位置1.2,上下文不会“粘”在上下文#2?

    也许使用using语句和异步方法会有一些魔力吗?

  2. 在2.2位,为什么上下文不是Context#2? 上下文是否应该转入“延续”(“等待”之后的陈述)?

码:

public class Test { public void StartHere() { SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); this.logCurrentSyncContext("1.1"); // Context #1 Task t = f(); this.logCurrentSyncContext("1.2"); // Context #1, why not Context #2? t.Wait(); this.logCurrentSyncContext("1.3"); // Context #1 } private async Task f() { using (new ThreadPoolSynchronizationContextBlock()) { this.logCurrentSyncContext("2.1"); // Context #2 await Task.Delay(7000); this.logCurrentSyncContext("2.2"); // Context is NULL, why not Context #2? } this.logCurrentSyncContext("2.3"); // Context #1 } // Just show the current Sync Context. Pass in some kind of marker so we know where, in the code, the logging is happening private void logCurrentSyncContext(object marker) { var sc = System.Threading.SynchronizationContext.Current; System.Diagnostics.Debug.WriteLine(marker + " Thread: " + Thread.CurrentThread.ManagedThreadId + " SyncContext: " + (sc == null? "null" : sc.GetHashCode().ToString())); } public class ThreadPoolSynchronizationContextBlock : IDisposable { private static readonly SynchronizationContext threadpoolSC = new SynchronizationContext(); private readonly SynchronizationContext original; public ThreadPoolSynchronizationContextBlock() { this.original = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(threadpoolSC); } public void Dispose() { SynchronizationContext.SetSynchronizationContext(this.original); } } } 

结果:

 1.1 Thread: 9 SyncContext: 37121646 // I call this "Context #1" 2.1 Thread: 9 SyncContext: 2637164 // I call this "Context #2" 1.2 Thread: 9 SyncContext: 37121646 2.2 Thread: 11 SyncContext: null 2.3 Thread: 11 SyncContext: 37121646 1.3 Thread: 9 SyncContext: 37121646 

2.2解释起来很简单, 1.2不那么容易。

2.2打印null的原因是当你await使用默认( new SynchronizationContext )或null SynchronizationContext时, Post方法将被调用传递给continuation委托,这是在ThreadPool上安排的 。 它没有努力恢复当前实例,当它们在ThreadPool(它是)上运行时,它依赖于当前的SynchronizationContext为这些延续为null 。 要清楚,因为您没有使用.ConfigureAwait(false)您的延续将按照您的预期发布到捕获的上下文,但此实现中的Post方法不会保留/流动相同的实例。

要修复此问题(即使您的上下文“粘滞”),您可以inheritanceSynchronizationContext ,并重载Post方法以使用已发布的委托调用SynchronizationContext.SetSynchronizationContext(this) (使用Delegate.Combine(...) )。 此外,内部在大多数地方将SynchronizationContext实例视为null ,因此如果您想要使用这些内容,请始终创建一个inheritance实现。

对于1.2 ,这实际上让我感到惊讶,因为我的理解是这将调用底层状态机(以及来自AsyncMethodBuilder所有内部),但它将在保持其SynchronizationContext同时被同步调用。

我认为我们在这里看到的内容在本文中有解释,它与在AsyncMethodBuilder / async状态机内捕获和恢复的ExecutionContext有关,这是保护和保留调用ExecutionContext ,从而保护SynchronizationContext 。 这里可以看到代码(感谢@VMAtm)。