Synchronization mechanism for an observable object

前端 未结 3 2175
情深已故
情深已故 2021-02-18 16:59

Let\'s imagine we have to synchronize read/write access to shared resources. Multiple threads will access that resource both in read and writing (most of times for reading, some

相关标签:
3条回答
  • 2021-02-18 17:47

    I'm not sure if this is exactly the same issue but when dealing with relatively small amounts of data (2k-3k entries), I have used the below code to facilitate cross thread read/write access to collections bound to UI. This code originally found here.

    public class BaseObservableCollection<T> : ObservableCollection<T>
    {
      // Constructors
      public BaseObservableCollection() : base() { }
      public BaseObservableCollection(IEnumerable<T> items) : base(items) { }
      public BaseObservableCollection(List<T> 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);
              }
            }
          }
        }
      }
    }
    

    I have also used the code below (which inherits from the above code) to support raising the CollectionChanged event when items inside the collection raise the PropertyChanged.

    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);
      }
    }
    
    0 讨论(0)
  • 2021-02-18 17:49

    Cross-Thread Collection Synchronization

    Putting a ListBox binding to a ObservableCollection , when the data changes , you update the ListBox because INotifyCollectionChanged implemented . The defect dell'ObservableCollection is that the data can be changed only by the thread that created it.

    The SynchronizedCollection does not have the problem of Multi-Thread but does not update the ListBox because it is not implemented INotifyCollectionChanged , even if you implement INotifyCollectionChanged , CollectionChanged (this, e) can only be called from the thread that created it .. so it does not work.

    Conclusion

    -If you want a list that is autoupdated mono-thread use ObservableCollection

    -If you want a list that is not autoupdated but multi-threaded use SynchronizedCollection

    -If you want both, use Framework 4.5, BindingOperations.EnableCollectionSynchronization and ObservableCollection () in this way :

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

    The Complete Sample http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

    0 讨论(0)
  • 2021-02-18 17:52

    This sounds like quite the multi-threading pickle. It's quite challenging to work with recursion in this chain-of-events pattern, whilst still avoiding deadlocks. You might want to consider designing around the problem entirely.

    For example, you could make the addition of an operand asynchronous to the raising of the event:

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

    And then have the actual addition happen in a background thread:

    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();
    }
    

    This separation sacrifices some consistency - code running after the add method won't actually know when the add has actually happened and maybe that's a problem for your code. If so, this could be re-written to subscribe to the observation and use a Task to signal when the add completes:

    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;
    }
    

    The event-handler logic is raised on the background thread pumping the add messages, so there is no possibility of it blocking your foreground threads. If you await the add on the message-pump for the window, the synchronization context is smart enough to schedule the continuation on the message-pump thread as well.

    Whether you go down the Task route or not, this strategy means that you can safely add more operands from an observable event without re-entering any locks.

    0 讨论(0)
提交回复
热议问题