相关文章推荐
悲伤的拖把  ·  一款软件无法正常打开,疑似WPF工作异常,是 ...·  2 周前    · 
高大的高山  ·  【wpf】ListView 和 ...·  1 周前    · 
气宇轩昂的春卷  ·  WPF ListView关闭选择开发者社区·  1 周前    · 
心软的夕阳  ·  如何使用networkx绘制带节点坐标的模拟 ...·  2 年前    · 
沉稳的麻辣香锅  ·  用python新窗口画图 ...·  2 年前    · 
体贴的匕首  ·  如何用repo强制和远程代码同步 - ...·  2 年前    · 
空虚的啄木鸟  ·  SQL INSERT INTO 语句 | 菜鸟教程·  2 年前    · 
活泼的酱肘子  ·  Matlab:*.txt转换为*.mat ...·  2 年前    · 
Code  ›  [WPF] 在单元测试中使用 Prism 的 EventAggregator,订阅到 ThreadOption.UIThread 会报错开发者社区
单元测试 wpf
https://cloud.tencent.com/developer/article/1780160
聪明伶俐的大熊猫
2 年前
作者头像
dino.c
0 篇文章

[WPF] 在单元测试中使用 Prism 的 EventAggregator,订阅到 ThreadOption.UIThread 会报错

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > dino.c的专栏 > [WPF] 在单元测试中使用 Prism 的 EventAggregator,订阅到 ThreadOption.UIThread 会报错

[WPF] 在单元测试中使用 Prism 的 EventAggregator,订阅到 ThreadOption.UIThread 会报错

作者头像
dino.c
发布 于 2021-01-27 10:02:40
624 0
发布 于 2021-01-27 10:02:40
举报

1. 问题

[TestClass]
public class UnitTest1
    [TestMethod]
    public void TestMethod1()
        ContainerLocator.Container.Resolve<TestViewModel>();
public class TestViewModel
    public TestViewModel(IEventAggregator eventAggregator)
        var testEvent = eventAggregator.GetEvent<TestEvent>();
        testEvent.Subscribe(() => { }, ThreadOption.UIThread);
public class TestEvent : PubSubEvent
}

上面是一段使用了 Prism 的单元测试,它主要的逻辑是在 EventAggregator 中订阅了 TestEvent,当接收到消息后在 UI 线程上执行后续的逻辑。这种代码在正常程序中没有问题,但在单元测试中会报错:

System.InvalidOperationException: To use the UIThread option for subscribing, the EventAggregator must be constructed on the UI thread.

2. 原因

翻翻源码,可以发现这个 Exception 在 PubSubEvent 的 Subscribe 函数中抛出:

switch (threadOption)
    case ThreadOption.PublisherThread:
        subscription = new EventSubscription(actionReference);
        break;
    case ThreadOption.BackgroundThread:
        subscription = new BackgroundEventSubscription(actionReference);
        break;
    case ThreadOption.UIThread:
        if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
        subscription = new DispatcherEventSubscription(actionReference, SynchronizationContext);
        break;
    default:
        subscription = new EventSubscription(actionReference);
        break;

当 SynchronizationContext 为 null 时就会判断当前不在 UI 线程,然后抛出 Exception。而 SynchronizationContext 又是在 EventAggregator 中赋值:

private readonly SynchronizationContext syncContext = SynchronizationContext.Current;
public TEventType GetEvent<TEventType>() where TEventType : EventBase, new()
    lock (events)
        EventBase existingEvent = null;
        if (!events.TryGetValue(typeof(TEventType), out existingEvent))
            TEventType newEvent = new TEventType();
            newEvent.SynchronizationContext = syncContext;
            events[typeof(TEventType)] = newEvent;
            return newEvent;
            return (TEventType)existingEvent;
}

问题就出在 SynchronizationContext.Current 这里。这个属性用于获取当前线程的同步上下文。不是每一个线程都有一个 SynchronizationContext 对象。一个总是有 SynchronizationContext 对象的是UI线程。由于单元测试并不是运行在 UI 线程,所以这个属性在单元测试中一直为 null。

3. 解决方案

现在我们知道问题原因了,解决方案也很简单,只要自定义一个 EventAggregator,源码全部照抄,但是把这句:

private readonly SynchronizationContext syncContext = SynchronizationContext.Current;

替换成这句:

private readonly SynchronizationContext syncContext = new SynchronizationContext();

就不会出现 PubSubEvent 中 SynchronizationContext 等于 null 的情况了。然后再把这个类注册到容器中作为 IEventAggregator:

ContainerLocator.Current.RegisterSingleton<IEventAggregator, MyEventAggregator>();

4. 最后

根据单元测试项目的结构,容器的初始化会有不同的方式,如果想尽量模仿 PrismApplication 的话可以参考 PrismApplicationBase 和 PrismInitializationExtensions 写一个初始化类,大概差不多这样(简化了部分代码):

[TestClass]
public abstract class TestInitializerBase
    public void Initialize()
        ContainerLocator.SetContainerExtension(() => new UnityContainerExtension());
        ContainerExtension = ContainerLocator.Current;
        ContainerExtension.RegisterSingleton<IDialogService, DialogService>();
        ContainerExtension.RegisterSingleton<IModuleInitializer, ModuleInitializer>();
        ContainerExtension.RegisterSingleton<IModuleManager, ModuleManager>();
        ContainerExtension.RegisterSingleton<RegionAdapterMappings>();
        ContainerExtension.RegisterSingleton<IRegionManager, RegionManager>();
        ContainerExtension.RegisterSingleton<IRegionNavigationContentLoader, RegionNavigationContentLoader>();
        ContainerExtension.RegisterSingleton<IEventAggregator, EventAggregator>();
        ContainerExtension.RegisterSingleton<IRegionViewRegistry, RegionViewRegistry>();
        ContainerExtension.RegisterSingleton<IRegionBehaviorFactory, RegionBehaviorFactory>();
        ContainerExtension.Register<IRegionNavigationJournalEntry, RegionNavigationJournalEntry>();
        ContainerExtension.Register<IRegionNavigationJournal, RegionNavigationJournal>();
        ContainerExtension.Register<IRegionNavigationService, RegionNavigationService>();
        RegisterRequiredTypes(ContainerExtension);
    public IContainerExtension ContainerExtension { get; private set; }
    protected abstract void RegisterRequiredTypes(IContainerRegistry containerRegistry);
public class TestInitializer : TestInitializerBase
    [AssemblyInitialize]
    public static void InitializeAseemble(TestContext testContext)
        var testInitializer = new TestInitializer();
        testInitializer.Initialize();
 
推荐文章
悲伤的拖把  ·  一款软件无法正常打开,疑似WPF工作异常,是不是.NET框架的问题? - Microsoft Q&A
2 周前
高大的高山  ·  【wpf】ListView 和 ItemsControl 的一点区别 - 宋桓公
1 周前
气宇轩昂的春卷  ·  WPF ListView关闭选择开发者社区
1 周前
心软的夕阳  ·  如何使用networkx绘制带节点坐标的模拟路网_networkx坐标_weixin_31140861的博客-CSDN博客
2 年前
沉稳的麻辣香锅  ·  用python新窗口画图 python创建图形窗口语句_mob6454cc6c40c9的技术博客_51CTO博客
2 年前
体贴的匕首  ·  如何用repo强制和远程代码同步 - caohong的个人空间 - OSCHINA - 中文开源技术交流社区
2 年前
空虚的啄木鸟  ·  SQL INSERT INTO 语句 | 菜鸟教程
2 年前
活泼的酱肘子  ·  Matlab:*.txt转换为*.mat - Cvdog - 博客园
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号