Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Thread Safe Improvement for ObservableCollection

5.00/5 (5 votes)
2 Jul 2012CPOL2 min read 63.7K   477  
Why didn’t Microsoft provide this in the ObservableCollection?

Introduction

I was working on a WPF project where I had the ViewModel mirroring the Model displaying a hierarchical view of the data (like a tree view, but using XamDataGrid). I wanted to have an event in the Model that triggered when a record was added or removed to reduce coupling (probably when changes are made in the Model in the future). This was because I expected to be using the Model side with many different ViewModels, although at this point not sure since I am being given the requirements piece-meal. When I did this in a case where I had a BackgroundWorker, I started having problems with being on the wrong thread. Of course I expected that, and I immediately implemented a Dispatcher on the receiving class using code like the following:

C#
Dispatcher.CurrentDispatcher.Invoke(new Action<StagedBlotterOrderAggViewModel>(Add), adding);

This did not work so I attempted to use DispatcherPriority. Nothing looked right to me, but I tried several options and none worked.

I could go back to a design of trying to ensure that the record updates were done in the BackgroundWorkerCompleted handler, but that would complicate the code.

Of course I know that you can get the Dispatcher from the control, but that was not very elegant. Therefore I started to search the web, and found something interesting at “Have worker thread update ObservableCollection that is bound to a ListCollectionView” (http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx). I was not sure it would work, but I tried it, and it did. Of course I guess eventually I would have come up with inheriting from ObservableCollection<T> since I knew that I could get the Dispatcher from the control and then would not have had to do anything special in the ViewModel, but I am sure this is better than what I would have come up with. I did make a small number of improvements of adding the other constructors.

Here is my slightly improved version of this code that worked so well for me:

C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Threading;
 
namespace Custom.Collections
{
  public class ObservableCollectionEx<t> : ObservableCollection<t>
  {
    // Override the event so this class can access it
    public override event NotifyCollectionChangedEventHandler CollectionChanged;
 
    public ObservableCollectionEx(IEnumerable<t> collection) : base(collection) { }
    public ObservableCollectionEx(List<t> collection) : base(collection) { }
 
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
      // Be nice - use BlockReentrancy like MSDN said
      using (BlockReentrancy())
      {
        var eventHandler = CollectionChanged;
        if (eventHandler != null)
        {
          Delegate[] delegates = eventHandler.GetInvocationList();
          // Walk thru invocation list
          foreach (NotifyCollectionChangedEventHandler handler in delegates)
          {
            var 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);
          }
        }
      }
    }
  }
}</t></t></t></t>

Now my big question is: Why didn’t Microsoft provide this in the original ObservableCollection?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)