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

时间:2017-04-20 01:11:50

标签: c# .net async-await using synchronizationcontext

我不了解以下代码的行为(但主要有一点)。

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

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

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

以下是我的问题:

  1. 当执行到达位置" 2.1"背景已变为     背景#2。好的。然后,因为我们点击了“等待”,任务就是     返回并执行跳回到位置" 1.2"。为什么然后,在     位置1.2,上下文不是"坚持"在情境#2?

    也许使用using语句和异步方法会有一些魔力吗?
  2. 在2.2位,为什么上下文不是Context#2?不应该将上下文转移到"继续" (“等待”之后的陈述)?
  3. 代码:

        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
    

1 个答案:

答案 0 :(得分:2)

2.2解释起来非常简单,1.2并不那么容易。

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

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

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

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