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

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

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

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

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

这是我的问题:

>
当执行到达位置“2.1”时,上下文已更改为
    背景#2.好的.然后,因为我们点击了“等待”,任务就是
    返回并执行跳回到位置“1.2”.为什么然后,在
    位置1.2,上下文不是“坚持”在上下文#2?
也许这里有一些使用using语句和异步方法的魔法?
>
在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的原因是当你等待使用默认(新的SynchronizationContext)或null SynchronizationContext时,Post方法将被调用传递给continuation委托,这个is scheduled on the ThreadPool.它没有努力恢复当前的实例,它依赖于当这些继续在ThreadPool(它是)上运行时,当前的SynchronizationContext为null.要清楚,因为您没有使用.ConfigureAwait(false),您的延续将按预期发布到捕获的上下文,但此实现中的Post方法不会保留/流动相同的实例.

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

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

我认为我们在这里看到的是in this post,它与在AsyncMethodBuilder /异步状态机内捕获和恢复的ExecutionContext有关,这是保护和保留调用ExecutionContext,从而保护SynchronizationContext.这个can been seen here的代码(感谢@VMAtm).

点赞