同步.Post上的上下文为null,但Send上的上下文不为null

本文关键字:上下文 null Send 同步 Post | 更新日期: 2023-09-27 18:02:44

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

我认为这个答案可能是一个很好的解决方法,但我最终使用了一个更简单的解决方案:在单元测试开始时显式设置同步上下文,直到您尝试读取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,就像你期望的异步调用一样。

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

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

同步.Post上的上下文为null,但Send上的上下文不为null

好的。所以我想,在这篇文章的帮助下,我明白了这一点。

如果您查看EventAggregator的源,当您使用ThreadOption Publish时。UiThread,你在告诉SynchronizationContext.CurrentPost

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

在我的示例(以及我的单元测试(中,我没有使用DispatcherSynchronizationContext,而是使用一个简单的jane SynchronizationContext,其Post的默认实现调用ThreadPool.QueueUserWorkItem。考虑到文档,这是一个令人困惑的默认实现——它可能真的应该是一个抽象方法。

无论如何,这个实现产生了一个新线程,这个新线程获得了一个全新的ExecutionContext,而该执行上下文的同步上下文在默认情况下为null。

我想这里需要注意的一点是,Prism并不关心同步上下文的类型——它只需要在解析EventAggregator时第一次访问它时存在一个引用。

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

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