WPF调度程序是解决多线程问题的方案吗?

19
我对在我的代码中使用锁定操作有一种不好的感觉,但现在WindowBase的Dispatcher存在了,我想在任何地方都使用它。
例如,我使用一个多线程单例WCF服务,在PRISM的EventAggregator上发布事件,有效负载是不可变的(只是数据),每个具有Dispatcher的线程都可以优雅地检索到事件,而不会在自己的Dispatcher中出现死锁。 (不仅是UI线程,还包括具有数据库调用、服务调用、日志或其他慢调用的线程,因为我不想冻结UI)。
但我的问题是,这个Dispatcher与WPF耦合在一起,因此当我在各处使用它时,我感到有点内疚,我感觉Dispatcher并不是为我的使用案例而创建的。
是否存在另一个未与WPF耦合的Dispatcher实现?或者滥用它是否OK?
谢谢,
更新
Paul Stovell给我的解决方案是创建一个IDispatcher接口和一个适配器来适配Wpf Dispatcher,这样测试就更容易了! 这个解决方案对我很好,因为我重构了我的测试,现在可以在我的测试中使用SynchronousDispatcherAdapter(感谢它,我不必在我的测试中使用WPF的Dispatcher)。
使用Dispatcher而不是BackgroundWorker是有意义的,因为我正在使用多发布者/订阅者模式(使用PRISM),并且由于Dispatcher,每个事件处理程序都在订阅它们的线程上调用。这意味着多线程问题可能发生的唯一点是在我的事件有效负载处(我使它不可变)。
我的不同线程之间不直接通信,它们只能发布和订阅事件。 因此,数据库调用、日志调用、服务调用、UI调用在不同线程上运行,并且彼此不知道(它们只知道它们订阅和发布的事件)。
当我从我的UI向存储库发出一些调用时,后台工作者将有意义。
但我希望找到一种设计,而无需使用BackgroundWorker,因为我更喜欢使用这种订阅/发布模式(我认为这使我的代码更易读)。
3个回答

16

使用Dispatcher(或BackgroundWorker)的主要问题在于,除非您的测试工具确实拥有UI线程,否则很难进行测试。

解决方案1

使用SynchronizationContext。它提供了与调用UI线程相同的功能,并可在Windows或WPF中使用。也可以进行测试

解决方案2

将Dispatcher视为另一个服务。正如您使用PRISM一样熟悉服务和IOC一样。以下是使用此类服务的方法:

// Not a UI component
public class MyDomainService : IMyDomainService
{
   private readonly IDispatcher _dispatcher;

   public MyDomainService(IDispatcher dispatcher) 
   {
      _dispatcher = dispatcher;
   }

   private void GotResultFromBackgroundThread()
   {
       _dispatcher.Dispatch(() => DoStuffOnForegroundThread());
   }
}

这使您可以在平台/测试中替换不同的实现。

这里是一个IDispatcher的示例,包括WPF实现测试实现。您可以像注册任何其他服务一样将它们注册到IOC容器中,然后它们可供UI和其他服务使用。


PRISM使用IDispatcherFacade,我已经扩展了他们的类CompositePresentationEvent<T>并添加了一个重载来订阅:SubscriptionToken Subscribe(Action<T>, IDispatcherFacade, bool, Predicate<T>)因此很容易进行测试(Wpf适配器编码简单明了)。 - Nicolas Dorier
1
你不必拥有UI线程,因为你可以在任何线程上使用Dispatcher.Current创建一个调度程序。 - Nicolas Dorier
啊,好的,对不起,我忘记了当我使用这个接口进行注入时,我不再与WPF紧耦合,谢谢;) - Nicolas Dorier

2

是和渲染有关,不是线程问题。

调度程序按优先级选择工作项并将每个工作项运行到完成。每个UI线程必须至少有一个Dispatcher,并且每个Dispatcher可以在一个线程中执行工作项。请参见此链接(来自Microsoft)。

您仍然需要自己处理任何启动的线程。

请查看这个链接以获取有关:基于事件的异步模式的多线程编程信息。

个人建议使用Background Worker来满足线程需求。

最佳实践可参考此处


BackgroundWorker使用调度程序,因为RunWorkerCompleted在UI线程上调用,所以我认为它与我的解决方案没有太大区别。使用异步事件模式时,必须小心保护共享资源,并使用锁定(我想避免这种情况)。使用Dispatcher对象可以有自己的线程。 - Nicolas Dorier
Slashene调度程序使用相同的UI线程。它以与后台工作器相同的方式将自身推送到Windows消息泵中,因此需要与后台工作器一样的锁定量。 - Paul Stovell
调度程序和BackgroundWorker之间的区别在于您可以决定代码将在哪个线程上执行。我只需要检索他的Dispatcher。而BackgroundWorker会创建一个新线程,然后在创建BackgroundWorker的调度程序线程上完成。 - Nicolas Dorier

0

我要复活这个帖子,但这听起来像是一个坏主意。你所说的是需要一个队列让发布者将项目放在其中供其订阅者使用。调度程序本质上只是一个被过度包装的队列,具有大量的开销。这些开销是专门用于保护对UI资源的访问,而你并没有使用它。这表明它不适合使用。

建议使用SynchronizationContext的人走在了正确的道路上。这可以安全地将数据传输到另一个线程,而不会将你绑定到UI概念。你可以编写一个扩展方法,将你的事件传输到每个事件订阅者请求的同步上下文中(通过将订阅委托的目标转换为ISynchronizeInvoke可用。该转换的结果将允许您知道是否需要进行传输,并可以自动为您执行此操作。

更好的方法是只使用具有适当锁定语义的队列。锁的开销不太可能成为问题,如果是的话,你使用调度程序将比简单的锁更具破坏性。在这种情况下,简单就是更好的。关键是只保留锁以添加/删除队列中的项目。你的订阅者应该在锁之外执行他们的工作。


这篇文章非常古老。现在,我想要做的完美类是System.Threading.Channels.Channel,但在此之前它并不存在。 - Nicolas Dorier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接