【问题标题】:Threadsafe observer pattern线程安全观察者模式
【发布时间】:2015-01-04 07:49:20
【问题描述】:

我正在以 MVC 模式编写 WPF 应用程序。应用程序的目的是显示数据库中的一些数据,这些数据是异步更新的。

我正在考虑如何设计架构,使其成为线程安全的。特别是:

  • 每个页面(或其视图模型)必须能够订阅和取消订阅服务,这会更新数据库。
  • 更新数据库的服务会通知所有订阅者,新数据已到达,他们应该刷新他们的视图。

显然,刚刚关闭的页面应该取消订阅服务,而刚刚出现的页面应该(或可能)订阅。

我可以将订阅放在关键部分中,以及广播新数据,然后想象以下场景(页面 ~= 它的视图模型,这在此处无关紧要):

  • 服务进入关键部分以广播有关新数据的信息(在单独的线程中)
  • 页面尝试进入临界区以取消订阅(在主线程中)
  • 服务将新数据通知页面(在单独的线程中)。
  • 页面填充其字段并引发 PropertyChange 事件(在单独的线程中)。
  • PropertyChange 事件被编组到主线程。等待关键部分。

在我看来,这就像一个僵局。

如何安全地设计此架构以避免此类死锁?也许页面永远不应该取消订阅?或者是否有其他方法可以保护线程不会死锁?

【问题讨论】:

  • 这看起来真的很奇怪。具体来说,“服务”如何知道某些数据是新的?是不是因为刚刚被自己的app提交了?
  • 为什么主线程需要获取临界区? (在最后一个要点)?另外,为什么在持有锁的同时触发事件?这很可能导致死锁!此外,一些演示问题的代码也会有所帮助。
  • 一般来说,one 资源不会出现死锁。要出现死锁,您必须有 2 个或更多受保护的资源,并且阻塞 -> 阻塞图中的封闭循环。您使用“关键部分”术语(这不是问题),但您必须明确您使用什么保护/互斥/信号/等,您有 2 个或更多还是只有一个。我的猜测是,在取消订阅代码部分运行(在主 UI 线程中)之后,编组(排队)的 PropertyChange 将运行而没有死锁。当然,如果数据结构无效,这可能会导致问题,但这与死锁无关。
  • @zaitsman:服务无法知道,这就是为什么它是 polling(我想)。 Spook 的架构服务器隐藏这种丑陋的轮询(来自数据库环境),并将轮询转换为事件驱动的方法。
  • @g.pickardou 这就是你阅读它的方式,以及我最初阅读它的方式。但我想在这一点上得到 OP 的确认。答案将有助于确定正确的方法。

标签: c# wpf multithreading windows-phone-8.1


【解决方案1】:

鉴于该帖子被标记为 WPF 和 WP-8.1 以及 cmets 中的澄清,我将执行以下操作:

  1. 让基础模型类(具有保存相关数据的属性)实现INotifyPropertyChanged
  2. 将所有页面的模型设置为ObservableCollection<BaseModel>。该模型还应实现在构造函数中实例化的互斥锁/锁属性。
  3. 在所有视图模型之间共享模型(例如共享模型实例)。
  4. 在执行异步操作的“服务”中,我将只使用模型本身的锁定对象lock 将模型ObservableCollection 中的AddRemove 项的代码部分。这部分必须放在Dispatcher.Invoke() 或等效的平台调用中。这可确保只有 UI 线程在等待更新集合。
  5. 我会将相关页面中的所有 UI 绑定到 viewmodel 中的模型引用。

这样,UI 和视图模型对特定的服务事件不关心,从而消除了订阅的开销,并且如果您共享模型,您还可以限制数据的重复 - 即使屏幕上有 20 个页面,您的服务也会执行通过框架(绑定)的力量传播到 UI 和视图模型的单个更新。

【讨论】:

  • (不评估整个答案只是指出错误)如果添加/删除操作放在 Dispatcher.Invoke() 中,则无需锁定:对 ObservableCollection 的所有访问序列化到UI 线程因此不会在 那个 资源上发生并发问题和竞争条件。
  • @g.pickardou 我考虑过,你是对的。但这将确保没有其他 cpde 能够修改集合(例如,如果这只是应用程序的一部分)。如果它只是一个线程,锁虽然具有误导性,但开销很小。
  • “这将确保没有其他代码能够修改”。锁不能确保这一点。任何(不稳定的)代码仍然可以修改集合,只是不使用相同的保护或不将修改操作编组到 UI 线程。锁本身不会锁定nothing。就像交通中的红灯,遵守它的司机是“同步”的,其他人可能会造成事故。因此,不需要同时锁定 序列化。
【解决方案2】:

一个简单的解决方案可能是:不要在 UI 线程中执行取消订阅操作。 (通常不要阻塞 UI 线程。)以异步方式执行,即开即忘。

或者,您可以查看Rx (Reactive Extensions) 的具体用途:以多线程方式实现观察者模式。


默默地“只是不退订”可能不是一个好主意。虽然我不知道您的实现细节,但如果事件处理程序是实例方法,那么服务将隐式保留对该实例的引用,并且根据引用链,可能会阻止您的页面或其他实例进行垃圾收集。


“或者有没有其他方法可以保护线程,使它们不会死锁?”目前在 .NET 框架中没有自动防止死锁的魔术。其他多线程环境可能会或可能不会提供自动死锁解决方案(注意:不是预防)服务,它可以检测死锁(发生后)并自动选择受害者。在 .NET 中, 您正在等待资源时发生的情况可能是一个例外。 (同样,这还没有实现)

【讨论】:

    猜你喜欢
    • 2012-02-03
    • 1970-01-01
    • 2013-12-02
    • 2013-03-05
    • 2015-12-31
    • 2011-06-23
    • 1970-01-01
    • 2023-03-30
    • 2016-02-20
    相关资源
    最近更新 更多