C# 可观测目标的同步机制
假设我们必须同步对共享资源的读/写访问。多个线程将以读和写的方式访问该资源(大多数情况下用于读,有时用于写)。我们还假设每次写入都会触发读取操作(对象是可观察的) 对于这个示例,我将想象这样一个类(请原谅语法和样式,这只是为了说明):C# 可观测目标的同步机制,c#,.net,multithreading,thread-synchronization,C#,.net,Multithreading,Thread Synchronization,假设我们必须同步对共享资源的读/写访问。多个线程将以读和写的方式访问该资源(大多数情况下用于读,有时用于写)。我们还假设每次写入都会触发读取操作(对象是可观察的) 对于这个示例,我将想象这样一个类(请原谅语法和样式,这只是为了说明): 操作数和结果的实现在此示例中没有意义。 现在,让我们设想一些观察操作数并将生成结果以放入结果: void AddNewOperand(Operand operand) { try { _container.Lock.EnterWriteLo
操作数
和结果
的实现在此示例中没有意义。
现在,让我们设想一些观察操作数
并将生成结果以放入结果
:
void AddNewOperand(Operand operand) {
try {
_container.Lock.EnterWriteLock();
_container.Operands.Add(operand);
}
finally {
_container.ExitReadLock();
}
}
我们的算术观察器将执行类似的操作,但要使用一个新元素,它将使用EnterReadLock()
锁定以获取操作数,然后使用EnterWriteLock()
添加结果(让我省略这方面的代码)。由于递归,这将产生一个异常,但是如果我设置了LockRecursionPolicy.SupportsRecursion
,那么我只会将代码打开到死锁(从):
默认情况下,ReaderWriterLockSlim的新实例是使用LockRecursionPolicy.NoRecursion标志创建的,不允许递归。对于所有新开发,都建议使用此默认策略,因为递归会引入不必要的复杂性,会使代码更容易出现死锁
为了清楚起见,我重复相关部分:
递归[…]使代码更容易出现死锁。
如果我对LockRecursionPolicy.SupportsRecursion没有错,那么如果我从同一个线程请求一个,比如说,读锁,然后其他人请求一个写锁,那么我将拥有一个死锁,那么MSDN所说的是有意义的。此外,递归也会以可测量的方式降低性能(如果我使用的是readwriterlocksim
而不是ReadWriterLock
或Monitor
,这不是我想要的)
问题:
最后,我的问题是(请注意,我不是要讨论一般的同步机制,我想知道这个生产者/可观察者/观察者场景的问题是什么):
- 在这种情况下,什么更好?为了避免
ReadWriterLockSlim
而使用Monitor
(即使在现实世界中,代码读取比写入要多得多)
- 放弃这种粗糙的同步?这甚至可能产生更好的性能,但它会使代码变得更加复杂(当然不是在本例中,而是在现实世界中)
- 我是否应该(从观察到的集合)使通知异步
- 还有什么我看不见的吗
我知道并没有最好的同步机制,所以我们使用的工具必须适合我们的情况,但我想知道是否有一些最佳实践,或者我只是忽略了线程和观察者之间的一些非常重要的东西(想象一下使用,但问题是一般性的,不局限于那个框架)
可能的解决方案?
我会尝试让事件(以某种方式)推迟:
第一个解决方案
每次更改都不会触发任何CollectionChanged
事件,而是保留在队列中。当提供程序(推送数据的对象)完成时,它将手动强制刷新队列(按顺序引发每个事件)。这可以在另一个线程中完成,甚至可以在调用线程中完成(但在锁之外)
它可能会工作,但会让一切变得不那么“自动化”(每个更改通知都必须由生产者自己手动触发,需要编写更多代码,到处都有更多bug)
第二个解决方案
另一个解决方案可能是提供对可观察集合的锁的引用。如果我将ReadWriterLockSlim
包装在自定义对象中(用于将其隐藏在易于使用的IDisposable
对象中),我可以添加一个ManualResetEvent
来通知以这种方式释放的所有锁集合本身可能会引发事件(同样在同一线程或另一线程中)
第三种解决方案
另一个想法是使事件异步。如果事件处理程序需要一个锁,那么它将被停止以等待它的时间范围。为此,我担心可能会使用大量线程(尤其是从线程池中)
老实说,我不知道这些是否适用于现实世界的应用程序(就个人而言,从用户的角度来看,我更喜欢第二个,但它意味着对所有内容进行自定义收集,它使收集了解线程,如果可能的话,我会避免)。我不想让代码变得过于复杂。这听起来像是多线程pickle。在这种事件链模式中使用递归非常具有挑战性,同时还要避免死锁。您可能需要考虑围绕问题完全设计。
例如,可以使操作数的添加与事件的引发异步:
private readonly BlockingCollection<Operand> _additions
= new BlockingCollection<Operand>();
public void AddNewOperand(Operand operand)
{
_additions.Add(operand);
}
这种分离牺牲了一些一致性——在add方法之后运行的代码实际上不知道何时实际发生了add,这可能是代码的一个问题。如果是这样的话,可以重新写入以订阅观察,并在添加完成时使用任务
发出信号:
public Task AddNewOperandAsync(Operand operand)
{
var tcs = new TaskCompletionSource<byte>();
// 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;
}
public Task addnewoperanasync(操作数操作数)
{
var tcs=new TaskCompletionSource();
//为完成此任务编写一个事件处理程序
NotifyCollectionChangedEventHandler onChanged=null;
onChanged=(发送方,e)=>
{
//这是我们添加的操作数的事件吗?
if(如NewItems.Contains(操作数))
{
//完成任务。
tcs.SetCompleted(0);
//删除事件处理程序。
_container.operans.CollectionChanged-=onChanged;
}
}
//钩住处理器。
_container.operans.CollectionChanged+=onChanged;
//执行添加。
_加
private readonly BlockingCollection<Operand> _additions
= new BlockingCollection<Operand>();
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();
}
public Task AddNewOperandAsync(Operand operand)
{
var tcs = new TaskCompletionSource<byte>();
// 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;
}
public class BaseViewableCollection<T> : BaseObservableCollection<T>
where T : INotifyPropertyChanged
{
// Constructors
public BaseViewableCollection() : base() { }
public BaseViewableCollection(IEnumerable<T> items) : base(items) { }
public BaseViewableCollection(List<T> 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);
}
}
/ / Creates the lock object somewhere
private static object _lock = new object () ;
...
/ / Enable the cross acces to this collection elsewhere
BindingOperations.EnableCollectionSynchronization ( _persons , _lock )