Fast and reliable monitored collection

ObservableCollection raise notifications for every action performed on them. Firstly, they do not need to add or remove calls, and secondly, they are not thread safe.

Doesn't that slow them down? Perhaps we have a faster alternative? Some say that an ICollectionView wrapped around an ObservableCollection quickly? This is true .

+26
thread-safety wpf observablecollection icollectionview
07 Oct '11 at 12:07
source share
4 answers

ObservableCollection can be fast if it wants . ObservableCollection

The code below is a very good example of a reliable and fast observable collection, and you can expand it as you wish.

 using System.Collections.Specialized; public class FastObservableCollection<T> : ObservableCollection<T> { private readonly object locker = new object(); /// <summary> /// This private variable holds the flag to /// turn on and off the collection changed notification. /// </summary> private bool suspendCollectionChangeNotification; /// <summary> /// Initializes a new instance of the FastObservableCollection class. /// </summary> public FastObservableCollection() : base() { this.suspendCollectionChangeNotification = false; } /// <summary> /// This event is overriden CollectionChanged event of the observable collection. /// </summary> public override event NotifyCollectionChangedEventHandler CollectionChanged; /// <summary> /// This method adds the given generic list of items /// as a range into current collection by casting them as type T. /// It then notifies once after all items are added. /// </summary> /// <param name="items">The source collection.</param> public void AddItems(IList<T> items) { lock(locker) { this.SuspendCollectionChangeNotification(); foreach (var i in items) { InsertItem(Count, i); } this.NotifyChanges(); } } /// <summary> /// Raises collection change event. /// </summary> public void NotifyChanges() { this.ResumeCollectionChangeNotification(); var arg = new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset); this.OnCollectionChanged(arg); } /// <summary> /// This method removes the given generic list of items as a range /// into current collection by casting them as type T. /// It then notifies once after all items are removed. /// </summary> /// <param name="items">The source collection.</param> public void RemoveItems(IList<T> items) { lock(locker) { this.SuspendCollectionChangeNotification(); foreach (var i in items) { Remove(i); } this.NotifyChanges(); } } /// <summary> /// Resumes collection changed notification. /// </summary> public void ResumeCollectionChangeNotification() { this.suspendCollectionChangeNotification = false; } /// <summary> /// Suspends collection changed notification. /// </summary> public void SuspendCollectionChangeNotification() { this.suspendCollectionChangeNotification = true; } /// <summary> /// This collection changed event performs thread safe event raising. /// </summary> /// <param name="e">The event argument.</param> protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { // Recommended is to avoid reentry // in collection changed event while collection // is getting changed on other thread. using (BlockReentrancy()) { if (!this.suspendCollectionChangeNotification) { NotifyCollectionChangedEventHandler eventHandler = this.CollectionChanged; if (eventHandler == null) { return; } // Walk thru invocation list. Delegate[] delegates = eventHandler.GetInvocationList(); foreach (NotifyCollectionChangedEventHandler handler in delegates) { // If the subscriber is a DispatcherObject and different thread. DispatcherObject dispatcherObject = handler.Target as DispatcherObject; if (dispatcherObject != null && !dispatcherObject.CheckAccess()) { // Invoke handler in the target dispatcher thread... // asynchronously for better responsiveness. dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, e); } else { // Execute handler as is. handler(this, e); } } } } } } 

Also, the ICollectionView , which is located above the ObservableCollection , is actively aware of the changes and performs filtering, grouping, sorting relatively quickly compared to any other source list.

Again, observable collections may not be the ideal answer for faster data updates, but they do their job very well.

+73
07 Oct '11 at 12:16
source share

Here is a compilation of some of the decisions I made. The idea of ​​the collection changed the introcation taken from the first answer.

It also seems that the "Reset" operation should be synchronous with the main thread, otherwise strange things happen with CollectionView and CollectionViewSource.

I think that since on "Reset" the handler tries to read the contents of the collection immediately, and they should already be in place. If you perform a “Reset” async and immediately add some items that are asynchronous, than recently added items can be added twice.

 public interface IObservableList<T> : IList<T>, INotifyCollectionChanged { } public class ObservableList<T> : IObservableList<T> { private IList<T> collection = new List<T>(); public event NotifyCollectionChangedEventHandler CollectionChanged; private ReaderWriterLock sync = new ReaderWriterLock(); protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { if (CollectionChanged == null) return; foreach (NotifyCollectionChangedEventHandler handler in CollectionChanged.GetInvocationList()) { // If the subscriber is a DispatcherObject and different thread. var dispatcherObject = handler.Target as DispatcherObject; if (dispatcherObject != null && !dispatcherObject.CheckAccess()) { if ( args.Action == NotifyCollectionChangedAction.Reset ) dispatcherObject.Dispatcher.Invoke (DispatcherPriority.DataBind, handler, this, args); else // Invoke handler in the target dispatcher thread... // asynchronously for better responsiveness. dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, args); } else { // Execute handler as is. handler(this, args); } } } public ObservableList() { } public void Add(T item) { sync.AcquireWriterLock(Timeout.Infinite); try { collection.Add(item); OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item)); } finally { sync.ReleaseWriterLock(); } } public void Clear() { sync.AcquireWriterLock(Timeout.Infinite); try { collection.Clear(); OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset)); } finally { sync.ReleaseWriterLock(); } } public bool Contains(T item) { sync.AcquireReaderLock(Timeout.Infinite); try { var result = collection.Contains(item); return result; } finally { sync.ReleaseReaderLock(); } } public void CopyTo(T[] array, int arrayIndex) { sync.AcquireWriterLock(Timeout.Infinite); try { collection.CopyTo(array, arrayIndex); } finally { sync.ReleaseWriterLock(); } } public int Count { get { sync.AcquireReaderLock(Timeout.Infinite); try { return collection.Count; } finally { sync.ReleaseReaderLock(); } } } public bool IsReadOnly { get { return collection.IsReadOnly; } } public bool Remove(T item) { sync.AcquireWriterLock(Timeout.Infinite); try { var index = collection.IndexOf(item); if (index == -1) return false; var result = collection.Remove(item); if (result) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); return result; } finally { sync.ReleaseWriterLock(); } } public IEnumerator<T> GetEnumerator() { return collection.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return collection.GetEnumerator(); } public int IndexOf(T item) { sync.AcquireReaderLock(Timeout.Infinite); try { var result = collection.IndexOf(item); return result; } finally { sync.ReleaseReaderLock(); } } public void Insert(int index, T item) { sync.AcquireWriterLock(Timeout.Infinite); try { collection.Insert(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } finally { sync.ReleaseWriterLock(); } } public void RemoveAt(int index) { sync.AcquireWriterLock(Timeout.Infinite); try { if (collection.Count == 0 || collection.Count <= index) return; var item = collection[index]; collection.RemoveAt(index); OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, item, index)); } finally { sync.ReleaseWriterLock(); } } public T this[int index] { get { sync.AcquireReaderLock(Timeout.Infinite); try { var result = collection[index]; return result; } finally { sync.ReleaseReaderLock(); } } set { sync.AcquireWriterLock(Timeout.Infinite); try { if (collection.Count == 0 || collection.Count <= index) return; var item = collection[index]; collection[index] = value; OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, value, item, index)); } finally { sync.ReleaseWriterLock(); } } } } 
+4
Apr 3 '14 at 12:34 on
source share

I can’t add comments because I'm not cool enough yet, but sharing this issue I encountered may be worth publishing, even if this is not quite the answer. I kept getting the “Out of Range Index” exception using this FastObservableCollection, due to BeginInvoke. Apparently, change notifications can be canceled before calling the handler, so to fix this, I passed the following as the fourth parameter for BeginInvoke called by the OnCollectionChanged method (as opposed to using the args one argument):

 dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 

Instead of this:

 dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, e); 

This fixed the “Out of Range Index” issue that I encountered. Here's a more detailed explanation / snpipet code: Where can I get a thread safe CollectionView?

+2
Jun 10 '13 at 22:12
source share

An example where a synchronized list of observables is created:

 newSeries = new XYChart.Series<>(); ObservableList<XYChart.Data<Number, Number>> listaSerie; listaSerie = FXCollections.synchronizedObservableList(FXCollections.observableList(new ArrayList<XYChart.Data<Number, Number>>())); newSeries.setData(listaSerie); 
0
Mar 26 '14 at 5:53
source share



All Articles