C# 线程安全观察者模式

C# 线程安全观察者模式,c#,wpf,multithreading,windows-phone-8.1,C#,Wpf,Multithreading,Windows Phone 8.1,我正在用MVC模式为WPF编写一个应用程序。应用程序的目的是在数据库中显示一些数据,并且这些数据正在异步更新 我正在考虑如何设计该体系结构,这样它将是线程安全的。特别是: 每个页面或其viewmodel必须能够订阅和取消订阅更新数据库的服务。 更新数据库的服务会通知所有订阅者新数据已经到达,他们应该刷新自己的视图。 显然,刚刚关闭的页面应该从服务中取消订阅,而刚刚出现的页面应该或可能订阅 我可以将订阅放在一个关键部分中,也可以广播新数据,但可以想象以下场景页面~=其viewmodel,这在这里并

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

我正在考虑如何设计该体系结构,这样它将是线程安全的。特别是:

每个页面或其viewmodel必须能够订阅和取消订阅更新数据库的服务。 更新数据库的服务会通知所有订阅者新数据已经到达,他们应该刷新自己的视图。 显然,刚刚关闭的页面应该从服务中取消订阅,而刚刚出现的页面应该或可能订阅

我可以将订阅放在一个关键部分中,也可以广播新数据,但可以想象以下场景页面~=其viewmodel,这在这里并不重要:

服务进入关键部分,在单独的线程中广播有关新数据的信息 页面试图在主线程中进入要取消订阅的关键部分 服务向页面通知单独线程中的新数据。 页面填充其字段并在单独的线程中引发PropertyChange事件。 PropertyChange事件被封送到主线程。等待关键部分。 在我看来这就像是一个僵局


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

鉴于帖子被标记为WPF和WP-8.1,并且在评论中进行了说明,我将执行以下操作:

将具有保存相关数据的属性的基本模型类实现INotifyPropertyChanged 将所有页面的模型设置为ObservableCollection。该模型还应该实现在构造函数中实例化的互斥/锁属性。 跨所有viewmodels共享模型,例如共享模型实例。 在执行异步操作的“服务”中,我只会使用模型本身的lock对象锁定代码中添加或删除模型ObservableCollection项的部分。此节必须放在Dispatcher.Invoke或等效的平台调用中。这确保只有UI线程在等待更新集合。 我会将相关页面中的所有UI绑定到viewmodel中的模型引用。
通过这种方式,UI和viewmodels对特定的服务事件漠不关心,从而消除了订阅的开销,并且如果共享模型,即使屏幕上有20页,也可以限制数据的重复,您的服务将执行单个更新,该更新通过框架绑定的功能传播到UI和viewmodels。

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

或者,您可以看看究竟是什么用于此目的:以多线程方式实现观察者模式

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


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

作为一种方法,这看起来真的很奇怪。具体来说,“服务”如何知道某些数据是新的?是不是因为它是你自己的应用程序提交的?为什么主线程需要获取关键部分?最后一点?还有,为什么您在持有锁时触发事件?这很可能导致死锁!另外,一些演示问题的代码也会有帮助。一般来说,一个资源不能出现死锁。要产生死锁,您必须有2个或更多受保护的资源,并且在blocking->blocked图中有一个闭合循环。您使用的“关键部分”术语不是问题,但您必须明确您使用的是什么防护/互斥/信号量/等等,您是否有2个或多个或只有一个。我猜,在主UI线程中运行unsubscribe代码部分之后,封送队列属性更改将在没有死锁的情况下运行。当然,如果数据结构无效,这可能会导致问题,但是这与死锁无关。@zaitsman:服务无法知道,我想这就是它轮询的原因。Spook的架构服务器隐藏了我所看到的这种投票丑陋
它来自数据库环境,并将轮询转换为事件驱动方法。@g.pickardou这就是您阅读它的方式,我最初也是这样阅读的。但我想得到OP对这一点的确认。这个问题的答案将有助于确定正确的方法。如果添加/删除操作放在Dispatcher中,则不会评估整个答案,而只是指出错误。调用则无需锁定:对序列化到UI线程的ObservableCollection的所有访问,因此不会出现并发问题和争用条件resource.@g.pickardou我考虑过了,你是对的。但这将确保没有其他cpde能够修改集合,例如,如果这只是应用程序的一部分。如果它只是一个线程,那么锁虽然具有误导性,但开销将是最小的。这将确保没有其他代码能够修改。锁不能保证这一点。任何不稳定的代码仍然可以修改集合,只是不使用相同的保护或不将修改操作编组到UI线程。锁本身不锁任何东西。就像交通中的红灯一样,遵守红灯的司机被同步化,其他人可能会导致事故。因此,不需要同时进行锁定和序列化。