Synchronization.Context 在 Post 上为空,但在 Send 上不为空

Synchronization.Context is null on Post but not on Send

我正在尝试对使用 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 是异步发生的,而发送是同步发生的,当我在线程调试 window 中观察它时,它实际上会跳转到另一个线程 ID,就像您希望的那样期待一个异步调用。

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

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

好的。所以我想我在 this article.

的帮助下解决了这个问题

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

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

在我的示例(和我的单元测试)中,我没有使用 DispatcherSynchronizationContext - 我使用的是普通简 SynchronizationContext,其 default implementation of Post makes a call to ThreadPool.QueueUserWorkItem. That's kind of a confusing default implementation 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);