Is there a Threadsafe Observable collection in .NET 4?

前端 未结 4 1769
南方客
南方客 2020-12-03 01:45

Platform: WPF, .NET 4.0, C# 4.0

Problem: In the Mainwindow.xaml i have a ListBox bound to a Customer collection which is currently an ObservableCollecti

4条回答
  •  感情败类
    2020-12-03 02:07

    There are two possible approaches. The first would be to inherit from a concurrent collection and add INotifyCollectionChanged functionality, and the second would be to inherit from a collection that implements INotifyCollectionChanged and add concurrency support. I think it is far easier and safer to add INotifyCollectionChanged support to a concurrent collection. My suggestion is below.

    It looks long but most of the methods just call the internal concurrent collection as if the caller were using it directly. The handful of methods that add or remove from the collection inject a call to a private method that raises the notification event on the dispatcher provided at construction, thus allowing the class to be thread safe but ensuring the notifications are raised on the same thread all the time.

    using System;
    using System.Collections;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Runtime.InteropServices;
    using System.Security.Permissions;
    using System.Windows.Threading;
    
    namespace Collections
    {
        /// 
        /// Concurrent collection that emits change notifications on a dispatcher thread
        /// 
        /// The type of objects in the collection
        [Serializable]
        [ComVisible(false)]
        [HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
        public class ObservableConcurrentBag : IProducerConsumerCollection,
            IEnumerable, ICollection, IEnumerable
        {
            /// 
            /// The dispatcher on which event notifications will be raised
            /// 
            private readonly Dispatcher dispatcher;
    
            /// 
            /// The internal concurrent bag used for the 'heavy lifting' of the collection implementation
            /// 
            private readonly ConcurrentBag internalBag;
    
            /// 
            /// Initializes a new instance of the ConcurrentBag class that will raise  events
            /// on the specified dispatcher
            /// 
            public ObservableConcurrentBag(Dispatcher dispatcher)
            {
                this.dispatcher = dispatcher;
                this.internalBag = new ConcurrentBag();
            }
    
            /// 
            /// Initializes a new instance of the ConcurrentBag class that contains elements copied from the specified collection 
            /// that will raise  events on the specified dispatcher
            /// 
            public ObservableConcurrentBag(Dispatcher dispatcher, IEnumerable collection)
            {
                this.dispatcher = dispatcher;
                this.internalBag = new ConcurrentBag(collection);
            }
    
            /// 
            /// Occurs when the collection changes
            /// 
            public event NotifyCollectionChangedEventHandler CollectionChanged;
    
            /// 
            /// Raises the  event on the 
            /// 
            private void RaiseCollectionChangedEventOnDispatcher(NotifyCollectionChangedEventArgs e)
            {
                this.dispatcher.BeginInvoke(new Action(this.RaiseCollectionChangedEvent), e);
            }
    
            /// 
            /// Raises the  event
            /// 
            /// 
            /// This method must only be raised on the dispatcher - use 
            /// to do this.
            /// 
            private void RaiseCollectionChangedEvent(NotifyCollectionChangedEventArgs e)
            {
                this.CollectionChanged(this, e);
            }
    
            #region Members that pass through to the internal concurrent bag but also raise change notifications
    
            bool IProducerConsumerCollection.TryAdd(T item)
            {
                bool result = ((IProducerConsumerCollection)this.internalBag).TryAdd(item);
                if (result)
                {
                    this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
                }
                return result;
            }
    
            public void Add(T item)
            {
                this.internalBag.Add(item);
                this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
            }
    
            public bool TryTake(out T item)
            {
                bool result = this.TryTake(out item);
                if (result)
                {
                    this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
                }
                return result;
            }
    
            #endregion
    
            #region Members that pass through directly to the internal concurrent bag
    
            public int Count
            {
                get
                {
                    return this.internalBag.Count;
                }
            }
    
            public bool IsEmpty
            {
                get
                {
                    return this.internalBag.IsEmpty;
                }
            }
    
            bool ICollection.IsSynchronized
            {
                get
                {
                    return ((ICollection)this.internalBag).IsSynchronized;
                }
            }
    
            object ICollection.SyncRoot
            {
                get
                {
                    return ((ICollection)this.internalBag).SyncRoot;
                }
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable)this.internalBag).GetEnumerator();
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable)this.internalBag).GetEnumerator();
            }
    
            public T[] ToArray()
            {
                return this.internalBag.ToArray();
            }
    
            void IProducerConsumerCollection.CopyTo(T[] array, int index)
            {
                ((IProducerConsumerCollection)this.internalBag).CopyTo(array, index);
            }
    
            void ICollection.CopyTo(Array array, int index)
            {
                ((ICollection)this.internalBag).CopyTo(array, index);
            }
    
            #endregion
        }
    }
    

提交回复
热议问题