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 )