【问题标题】:Synchronization.Context is null on Post but not on SendSynchronization.Context 在 Post 上为空,但在 Send 上不为空
【发布时间】:2015-07-14 04:10:12
【问题描述】:

我正在尝试对使用 Prism 事件聚合器的应用程序中的某些行为进行单元测试。我尝试进行单元测试的代码的其中一件事是订阅 UI 线程上的事件。深入研究EventAggregator's implementation,我发现它是通过SynchronizationContext.Post 实现的。

我认为this answer 可能是一个很好的解决方法,但我最终使用了一个更简单的解决方法:在单元测试开始时显式设置同步上下文 - 在您尝试阅读 SynchronizationContext.Current 之前,该方法一直有效

这导致了我不完全理解的行为:

//set the sync context
var thisSyncContext = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(thisSyncContext);

thisSyncContext.Post(cb => {
    var ctx = SynchronizationContext.Current; //<-- this is null
    var equals = thisSyncContext.Equals(ctx); //<-- this is false
},null);

thisSyncContext.Send(cb => {
    var ctx = SynchronizationContext.Current; //<-- this is not null
    var equals = thisSyncContext.Equals(ctx); //<-- this is true
}, null);

我知道 Post 是异步发生的,而 Send 是同步发生的,当我在线程调试窗口中观察它时,它实际上会跳转到不同的线程 ID,正如您所期望的异步调用所做的那样。

我想我想理解的是,当我告诉同步上下文执行一个函数时,无论是同步还是异步,我都希望该上下文被保留。它保留用于同步调用,但不用于异步。

为什么会出现这种行为,如何在单元测试中对其进行补偿?

【问题讨论】:

    标签: c# .net unit-testing synchronizationcontext


    【解决方案1】:

    好的。所以我想我在this article 的大力帮助下想通了。

    如果您查看 source for EventAggregator,当您使用 ThreadOption.UiThread Publish 时,您是在告诉 SynchronizationContext.CurrentPost

    在 WPF 应用程序中运行时,SynchronizationContext.CurrentDispatcherSynchronizationContext 的一个实例,其 implementation of Post 异步将我们踢回原始 UI 线程,正如我们所期望的那样......

    在我的示例(和我的单元测试)中,我没有使用DispatcherSynchronizationContext - 我使用的是普通的简SynchronizationContext,其default implementation of Post 调用ThreadPool.QueueUserWorkItem。这是一种令人困惑的默认实现given the documentation - 它真的很可能应该是一个抽象方法。

    无论如何,这个实现产生了一个新线程,新线程获得一个新的 ExecutionContext,以及该执行上下文的同步上下文 by default, is null

    我想这里要注意的一点是 Prism 并不关心同步上下文是什么类型 - 它只需要一个引用来存在 first time it accesses it when the EventAggregator is resolved

    所以这里的解决方案是创建我们自己的同步上下文,将预期的异步行为替换为同步行为。

    /// <summary>
    /// Prism's UI thread option works by invoking Post on the current synchronization context.
    /// When we do that, base.Post actually looses SynchronizationContext.Current
    /// because the work has been delegated to ThreadPool.QueueUserWorkItem.
    /// This implementation makes our async-intended call behave synchronously,
    /// so we can preserve and verify sync contexts for callbacks during our unit tests.
    /// </summary>
    internal class MockSynchronizationContext : SynchronizationContext
    {
        public override void Post(SendOrPostCallback d, object state)
        {
            d(state);
        }
    }
    

    就我的单元测试而言,我不需要事件发布的异步响应,但我确实需要验证用于 UI 线程的订阅是否在启动单元测试的线程上执行。

    现在,当我们运行以下代码时:

    //set the sync context
    var thisSyncContext = new MockSynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(thisSyncContext);
    
    thisSyncContext.Post(cb => {
      var ctx = SynchronizationContext.Current; //<-- this is not null
      var equals = thisSyncContext.Equals(ctx); //<-- this is true
    },null);
    
    thisSyncContext.Send(cb => {
      var ctx = SynchronizationContext.Current; //<-- this is not null
      var equals = thisSyncContext.Equals(ctx); //<-- this is true
    }, null);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-14
      • 2012-04-24
      • 2015-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多