发布

时间:2015-07-14 04:10:13

标签: c# .net unit-testing synchronizationcontext

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

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

这引导我采取一种我并不完全理解的行为:

SynchronizationContext.Current

我理解Post是异步发生的,并且Send同步发生,当我在线程调试窗口中观察它时,它实际上会踢到不同的线程ID,就像你期望的异步调用一样。

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

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

1 个答案:

答案 0 :(得分:4)

确定。所以我认为我在this article提供了大量帮助后想出了这一点。

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

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

在我的示例(以及我的单元测试)中,我没有使用DispatcherSynchronizationContext - 我正在使用简明SynchronizationContext,其default implementation of Post拨打{ {1}}。这是一个令人困惑的默认实现given the documentation - 它可能应该是一个抽象的方法。

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

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

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

ThreadPool.QueueUserWorkItem

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

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

/// <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);
    }
}