Csharp/C#教程:可观察对象的同步机制分享


可观察对象的同步机制

让我们假设我们必须同步对共享资源的读/写访问。 多个线程将在读取和写入时访问该资源(大多数时间用于读取,有时用于写入)。 让我们假设每次写入总是会触发读操作(对象是可观察的)。

对于这个例子,我会想象一个像这样的类(原谅语法和风格,它仅用于说明目的):

class Container { public ObservableCollection Operands; public ObservableCollection Results; } 

我很想将ReadWriterLockSlim用于此目的而且我将它放在Container级别(想象对象不那么简单,一个读/写操作可能涉及多个对象):

 public ReadWriterLockSlim Lock; 

OperandResult实现对于此示例没有意义。 现在让我们设想一些观察Operands代码,并生成一个放入Results

 void AddNewOperand(Operand operand) { try { _container.Lock.EnterWriteLock(); _container.Operands.Add(operand); } finally { _container.ExitReadLock(); } } 

我们的hypotetical观察者会做类似的事情,但是要使用一个新元素,它将使用EnterReadLock()锁定以获取操作数,然后使用EnterWriteLock()来添加结果(让我省略代码)。 这会因为递归而产生exception,但是如果我设置了LockRecursionPolicy.SupportsRecursion那么我只需将代码打开到LockRecursionPolicy.SupportsRecursion (来自MSDN ):

默认情况下,使用LockRecursionPolicy.NoRecursion标志创建ReaderWriterLockSlim的新实例,并且不允许递归。 建议对所有新开发使用此默认策略,因为递归会引入不必要的复杂性并使您的代码更容易出现死锁

为清楚起见,我重复相关部分:

递归[…]使您的代码更容易出现死锁。

如果我对LockRecursionPolicy.SupportsRecursion没有错,如果从同一个线程我问一个,比如读锁定然后别人要求写锁定然后我会有一个死锁然后MSDN说的有意义。 此外,递归也会以可测量的方式降低性能(如果我使用的是ReadWriterLockSlim而不是ReadWriterLockMonitor ,则不是我想要的)。

问题(S)

最后我的问题是(请注意我不是在寻找关于通用同步机制的讨论,我知道这个生成器/ observable / observer场景有什么问题 ):

我知道没有最好的同步机制,所以我们使用的工具必须适合我们的情况,但我想知道是否有一些最佳实践,或者我只是忽略线程和观察者之间非常重要的东西(想象一下使用Microsoft Reactive Extensions但是问题是一般的,不依赖于该框架)。

可能的解决方案?

我会尝试将事件(某种程度上)推迟:

第一解决方案
每次更改都不会触发任何CollectionChanged事件,它会保留在队列中。 当提供者(推送数据的对象)完成时,它将手动强制刷新队列(按顺序引发每个事件)。 这可以在另一个线程中完成,甚至可以在调用者线程中完成(但在锁定之外)。

它可能有效,但它会使一切都不那么“自动”(每个更改通知必须由生产者本身手动触发,更多代码编写,更多的错误)。

第二解决方案
另一种解决方案可能是提供对可观察集合的锁定的引用。 如果我将ReadWriterLockSlim包装在一个自定义对象中(用于将其隐藏在一个易于使用的IDisposable对象中),我可以添加一个ManualResetEvent来通知所有锁已经以这种方式释放集合本身可能会引发事件(同样在相同的线程中或另一个线程)。

第三解决方案
另一个想法可能是使事件异步。 如果事件处理程序需要锁定,那么它将被停止以等待它的时间范围。 为此我担心可能使用的大线程数量(特别是如果来自线程池)。

老实说,我不知道这些是否适用于现实世界的应用程序(个人 – 从用户的角度来看 – 我更喜欢第二个,但它意味着所有内容的自定义集合,它使集合意识到线程,我会避免它,如果可能)。 我不想让代码比必要的更复杂。

这听起来像是multithreading的泡菜。 在这种事件链模式中使用递归是非常具有挑战性的,同时仍然避免死锁。 您可能想要考虑完全围绕问题进行设计。

例如,您可以使操作数的添加与事件的引发异步:

 private readonly BlockingCollection _additions = new BlockingCollection(); public void AddNewOperand(Operand operand) { _additions.Add(operand); } 

然后在后台线程中进行实际添加:

 private void ProcessAdditions() { foreach(var operand in _additions.GetConsumingEnumerable()) { _container.Lock.EnterWriteLock(); _container.Operands.Add(operand); _container.Lock.ExitWriteLock(); } } public void Initialize() { var pump = new Thread(ProcessAdditions) { Name = "Operand Additions Pump" }; pump.Start(); } 

这种分离牺牲了一些一致性 – 在add方法实际上不知道添加实际发生的时间之后运行的代码,这可能是您的代码的问题。 如果是这样,可以重写这个以订阅观察并使用Task在添加完成时发出信号:

 public Task AddNewOperandAsync(Operand operand) { var tcs = new TaskCompletionSource(); // Compose an event handler for the completion of this task NotifyCollectionChangedEventHandler onChanged = null; onChanged = (sender, e) => { // Is this the event for the operand we have added? if (e.NewItems.Contains(operand)) { // Complete the task. tcs.SetCompleted(0); // Remove the event-handler. _container.Operands.CollectionChanged -= onChanged; } } // Hook in the handler. _container.Operands.CollectionChanged += onChanged; // Perform the addition. _additions.Add(operand); // Return the task to be awaited. return tcs.Task; } 

事件处理程序逻辑在后台线程上引发添加消息,因此不可能阻塞前台线程。 如果等待窗口的消息泵上的添加,同步上下文足够智能,也可以在消息泵线程上安排继续。

无论您是否沿着Task路线前进,此策略意味着您可以安全地从可观察事件中添加更多操作数,而无需重新输入任何锁定。

我不确定这是否完全相同,但在处理相对少量的数据(2k-3k条目)时,我使用下面的代码来促进对绑定到UI的集合的跨线程读/写访问。 此代码最初在此处找到。

 public class BaseObservableCollection : ObservableCollection { // Constructors public BaseObservableCollection() : base() { } public BaseObservableCollection(IEnumerable items) : base(items) { } public BaseObservableCollection(List items) : base(items) { } // Evnet public override event NotifyCollectionChangedEventHandler CollectionChanged; // Event Handler protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { // Be nice - use BlockReentrancy like MSDN said using (BlockReentrancy()) { if (CollectionChanged != null) { // Walk thru invocation list foreach (NotifyCollectionChangedEventHandler handler in CollectionChanged.GetInvocationList()) { DispatcherObject dispatcherObject = handler.Target as DispatcherObject; // If the subscriber is a DispatcherObject and different thread if (dispatcherObject != null && dispatcherObject.CheckAccess() == false) { // Invoke handler in the target dispatcher's thread dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e); } else { // Execute handler as is handler(this, e); } } } } } } 

我还使用下面的代码(inheritance自上面的代码)来支持在CollectionChanged中的项引发PropertyChanged时引发CollectionChanged事件。

 public class BaseViewableCollection : BaseObservableCollection where T : INotifyPropertyChanged { // Constructors public BaseViewableCollection() : base() { } public BaseViewableCollection(IEnumerable items) : base(items) { } public BaseViewableCollection(List items) : base(items) { } // Event Handlers private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e) { var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender); base.OnCollectionChanged(arg); } protected override void ClearItems() { foreach (T item in Items) { if (item != null) { item.PropertyChanged -= ItemPropertyChanged; } } base.ClearItems(); } protected override void InsertItem(int index, T item) { if (item != null) { item.PropertyChanged += ItemPropertyChanged; } base.InsertItem(index, item); } protected override void RemoveItem(int index) { if (Items[index] != null) { Items[index].PropertyChanged -= ItemPropertyChanged; } base.RemoveItem(index); } protected override void SetItem(int index, T item) { if (item != null) { item.PropertyChanged += ItemPropertyChanged; } base.SetItem(index, item); } } 

跨线程集合同步

将ListBox绑定放到ObservableCollection中 ,当数据发生更改时,更新ListBox,因为实现了INotifyCollectionChanged。 缺陷dell’ObservableCollection是数据只能由创建它的线程更改。

SynchronizedCollection没有multithreading的问题,但是没有更新ListBox,因为它没有实现INotifyCollectionChanged,即使你实现了INotifyCollectionChanged,CollectionChanged(this,e)也只能从创建它的线程中调用..所以它不起作用。

结论

– 如果你想要一个自动更新的单线程列表使用ObservableCollection

– 如果您想要一个未自动更新但multithreading的列表,请使用SynchronizedCollection

如果需要两者,请以这种方式使用Framework 4.5 ,BindingOperations.EnableCollectionSynchronization和ObservableCollection():

 / / Creates the lock object somewhere private static object _lock = new object () ; ... / / Enable the cross acces to this collection elsewhere BindingOperations.EnableCollectionSynchronization ( _persons , _lock ) 

完整示例 http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

上述就是C#学习教程:可观察对象的同步机制分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/cdevelopment/962636.html

(0)
上一篇 2021年11月27日
下一篇 2021年11月27日

精彩推荐