线程安全观察器模式
本文关键字:模式 观察 安全观 安全 线程 | 更新日期: 2023-09-27 18:35:49
我正在以MVC模式为WPF编写应用程序。应用程序的目的是在数据库中显示一些数据,并且这些数据正在异步更新。
我正在考虑如何设计架构,使其是线程安全的。特别:
- 每个页面(或其视图模型)必须能够订阅和取消订阅服务,这将更新数据库。
- 更新数据库的服务通知所有订阅者,新数据已到达,他们应刷新其视图。
取消订阅服务,而刚刚出现的页面应该(或可能)订阅。
我可以将订阅放在关键部分,也可以广播新数据,但随后想象一下以下场景(页面 ~= 它的视图模型,这里无关紧要):
- 服务进入关键部分以广播有关新数据的信息(在单独的线程中)
- 页面尝试进入关键部分以取消订阅(在主线程中)
- 服务通知页面有关新数据的信息(在单独的线程中)。
- 页面填充其字段并引发属性更改事件(在单独的线程中)。
- 属性更改事件被封送到主线程。等待关键部分。
对我来说,这看起来像是一个僵局。
如何安全地设计此体系结构以避免此类死锁?也许页面永远不应该取消订阅?或者有没有另一种方法来保护线程,使它们不会死锁?
鉴于该帖子被标记为WPF和WP-8.1以及评论中的澄清,我会执行以下操作:
- 让基模型类(具有保存相关数据的属性的类)实现
INotifyPropertyChanged
- 将所有页面的模型作为
ObservableCollection<BaseModel>
。该模型还应实现在构造函数中实例化的互斥/锁属性。 - 在所有视图模型之间共享模型(例如,共享模型的实例)。
- 在执行异步操作的"服务"中,我只会
lock
使用模型本身的锁定对象从模型可观察集合中Add
或Remove
项的代码部分。此部分必须放在Dispatcher.Invoke()
或等效平台调用中。这可确保只有 UI 线程在等待更新集合。 - 我会将相关页面中的所有 UI 绑定到视图模型中的模型引用。
对特定的服务事件产生粗心,从而消除了订阅的开销,并且如果您共享模型,您还可以限制数据的重复 - 即使屏幕上有 20 页,您的服务也会执行单个更新,该更新通过框架(绑定)的力量传播到 UI 和视图模型。
一个简单的解决方案可能是: 不要在 UI 线程中执行取消订阅操作。(通常不会阻止 UI 线程。以异步方式进行,即发即弃。
或者,你可以看看Rx(反应式扩展)到底是什么用于这个目的:以多线程方式实现观察者模式。
默默地"只是不退订"可能不是一个好主意。虽然我不知道您的实现细节,但如果事件处理程序是实例方法,那么服务将隐式保留对该实例的引用,并且根据引用链,您的页面或其他实例可能会被阻止垃圾回收。
"或者有没有另一种方法来保护线程,使它们不会死锁?"目前在.NET框架中,没有自动防止死锁的魔术。其他多线程环境可能会也可能不会提供自动死锁解决(注意:不是预防)服务,该服务可以检测死锁(在死锁发生后)并自动选择受害者。在 .NET 中,等待资源时发生的情况可能是一个例外。(同样,这尚未实现)