【问题标题】:Is WPF Dispatcher the solution of multi threading problems?WPF Dispatcher 是多线程问题的解决方案吗?
【发布时间】:2009-01-28 07:23:11
【问题描述】:

我对在我的代码中使用 lock 有一种非常不好的感觉,但是现在 WindowBase 的 Dispatcher 存在,我想在任何地方使用它。

例如,我使用多线程单例 WCF 服务,它在 PRISM 的 EventAggregator 上发布事件,有效负载是不可变的(它只是数据),并且每个带有调度程序的线程都可以优雅地检索事件,而不会在自己的调度程序中出现死锁. (不仅是 UI 线程,还有数据库调用线程、服务调用线程、日志线程或其他调用慢的线程,因为我不想冻结 UI)。

但是我的问题是这个 Dispatcher 是和 WPF 耦合的,所以当我到处使用它的时候我感到有点内疚,我觉得这个 Dispatcher 不是为我的用例而创建的。

是否存在不与 WPF 耦合的另一个 Dispatcher 实现?还是可以滥用它?

谢谢,

更新

Paul Stovell 给我的解决方案是创建一个接口 IDispatcher,以及一个用于 Wpf Dispatcher 的适配器,这样会更容易测试! 这个解决方案对我有好处,因为我重构了我的测试,现在我可以在我的测试中使用 SynchronousDispatcherAdapter(多亏了它,我不必在我的测试中使用 WPF 的 Dispatcher)。

使用 Dispatcher 而不是 BackgroundWorker 是有意义的,因为我使用的是多发布者/订阅者模式(使用 PRISM),并且感谢 Dispatcher,每个事件处理程序都在订阅事件的线程上调用。这意味着多线程问题可能发生的唯一点是我的事件的有效负载(我让他不可变)。

我的不同线程之间不直接通信,它们只能发布和订阅事件。 因此,数据库调用、日志调用、服务调用、UI 调用在不同的线程上运行并且彼此不了解(它们只知道他们订阅和发布的事件)。

当我从我的 UI 对存储库进行一些调用时,后台工作人员会很有意义。

但我希望找到不使用 BackgroundWorker 的设计,因为我更喜欢使用这种订阅者/发布者模式(我认为它使我的代码更具可读性)

【问题讨论】:

    标签: wpf multithreading dispatcher


    【解决方案1】:

    使用 Dispatcher(或 BackgroundWorker)的主要问题是很难测试,除非您的测试工具实际上有一个 UI 线程。

    解决方案 1

    使用SynchronizationContext。它提供了在 UI 线程上调用的相同能力,并在 Windows 或 WPF 中工作。测试它also possible

    解决方案 2

    将调度程序视为另一种服务。当您使用 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());
       }
    }
    

    这允许您为您的平台/测试替换不同的实现。

    这是IDispatcherWPF implementationtest implementation 的示例。您可以像任何其他服务一样将它们注册到您的 IOC 容器中,并且它们可用于 UI 和其他服务。

    【讨论】:

    • PRISM 使用 IDispatcherFacade,我扩展了他们的类 CompositePresentationEvent 并为订阅添加了重载:SubscriptionToken Subscribe(Action, IDispatcherFacade, bool, Predicate) 所以它是易于测试(Wpf 适配器易于编码)
    • 而且您不必拥有 UI 线程,因为您可以使用 Dispatcher.Current 在任何线程上创建调度程序
    • 啊,对不起,我忘记了,当我使用这个接口进行注入时,我不再与 WPF 耦合了,谢谢 ;)
    【解决方案2】:

    是的,不是..它是渲染的东西..本身不是线程的东西..

    调度程序根据优先级选择工作项并运行每个工作项以完成。每个 UI 线程必须至少有一个 Dispatcher,并且每个 Dispatcher 只能在一个线程中执行工作项。根据来自 Microsoft 的 this link

    您仍然必须自己处理您自己启动的任何线程。

    查看this one 以获取有关信息:使用基于事件的异步模式进行多线程编程

    我个人使用Background Worker 来满足我的线程需求。

    Best Practices here.

    【讨论】:

    • BackgroundWorker 使用调度程序,因为在 UI 线程上调用 RunWorkerCompleted,所以我认为它与我的解决方案没有太大区别。使用异步事件模式,您必须使用锁小心地保护共享资源(我想避免这种情况)。有了 Dispatcher 对象就有了自己的线程。
    • Slashene,调度器使用相同的 UI 线程。它以与后台工作人员相同的方式将自身推送到 Windows 消息泵上,因此它需要与后台工作人员相同的锁定量。
    • dispatcher 和 BackgroundWorker 的区别在于你可以决定你的代码将在哪个线程上执行。我只需要找回他的调度员。而 BackgroundWorker 创建一个新线程,然后在创建 BackgroundWorker 的调度程序线程上完成。
    【解决方案3】:

    我要彻底解决这个问题,但这听起来是个坏主意。您的意思是,您需要一个队列让您的发布者为其订阅者转储项目。 Dispatcher 的核心只是一个美化的队列,周围有很多开销。开销专门用于保护对您未使用的 UI 资源的访问。这表明使用它是错误的。

    建议 SynchronizationContext 的人走上了一条好路。这实现了您想要的(安全地将数据编组到另一个线程),而无需将您束缚于 UI 概念。您可以编写一个扩展方法,将您的事件编组到事件的每个订阅者请求的 SynchronizationContext(它可以通过将订阅委托的目标转换为 ISynchronizeInvoke 来获得。该转换的结果将让您知道它是否需要编组,并且可以自动为您完成。

    最好只使用具有适当锁定语义的队列。锁的开销不太可能成为问题,如果是,您对 Dispatcher 的使用将比简单的锁更具破坏性。在这种情况下,越简单越好。他们的关键是只保留锁定以从队列中添加/删除项目。您的订阅者应该执行他们在锁定之外所做的任何工作。

    【讨论】:

    • 这篇文章已经很老了。如今,我想要做的最完美的课程是System.Threading.Channels.Channel,但这之前并不存在
    猜你喜欢
    • 1970-01-01
    • 2013-08-25
    • 2015-05-31
    • 2011-03-29
    • 2020-12-15
    • 2012-01-25
    • 2016-09-10
    • 2012-01-28
    相关资源
    最近更新 更多