Tutorials Forums
     Tutorials Videos
        Sign Up Now For FREE
Welcome, Guest
Username Password: Remember me

Multi-Threaded ObservableCollection and NotifyCollectionChanged
(1 viewing) (1) Guest
  • Page:
  • 1

TOPIC: Multi-Threaded ObservableCollection and NotifyCollectionChanged

Multi-Threaded ObservableCollection and NotifyCollectionChanged 12 Mar 2010 01:56 #304

Introduction
Yesterday I ran across a web-page on the internet where people were voting for enhancements to the next .NET Framework. On the page I noticed there were a lot of people voting for a safe multi-threaded way of dispatching an INotifyCollectionChanged event, mostly regarding the ObservableCollection. That made me remember the initial pains I had with this issue and the many articles devoted to trying to remedy it, one of the earliest if not first solutions coming from Bea Stollnitz (love her blog). I have long since put this behind me since I solved it on my own a few years ago but completely forgot that it's still very much the pain in the neck it was back then, so I've decided to pull the relevant classes out of my library and share it with everyone.

The good thing about my solution is that it's simple, performant, and doesn't have the limitations of most of the other solutions I've seen online such as the inability to modify the collection, or the need to keep two collections internally and keep them in sync, or the necessity of passing in a Dispatcher.

I've included two classes for use : the NotifyCollectionChanged and MTObservableCollection classes. The NotifyCollectionChanged class should be the only class you use since it wraps any existing object that derives from INotifyCollectionChanged, including ObservableCollection. The MTObservableCollection class is a multi-threaded version of the ObservableCollection but there should really be no need to use it; I just included it in case anyone needed it for some ungodly reason.
Background
There are already numerous articles out there detailing the issue in depth so I'll only give a brief synopsis here.

The problem, in a nutshell, is that the INotifyCollectionChanged event, when triggered from a thread other than the originating thread, will give you the infamous "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." You can test this yourself by creating an ObservableCollection and binding it to an ItemsControl/ListBox/ListView; note that when you do this you are doing it on the UI thread. Then, add an item to it via a worker thread, such as through a Timer event or ThreadPool.QueueUserWorkItem and you'll get an exception. The problem that occurs is not that you can't add or remove items from different threads; it's that the NotifyCollectionChanged event (the one your ItemsControl/ListBox/ListView automagically subscribes to in order to determine if something has been added or removed from the collection) that gets triggered is running from the worker thread, *not* from the UI thread, and since the worker thread doesn't have ANY mechanism for updating the UI it throws an exception. This explanation is a tad-bit simplified but you get the idea
Using the code
The magic to solving this problem in a simple manner happens when you realize that the problem only occurs at the notification side. If I want to be notified of an event, what would I have to do? I would have to subscribe to it of course. This is where the magic happens; it happens during the subscription process.

What I've done is override the CollectionChanged subscription mechanism so that when (given our scenario above) the UI control subscribes to the underlying collection's CollectionChanged event, I save the Dispatcher associated with the UI control.
#region INotifyCollectionChanged Members
private Dictionary<NotifyCollectionChangedEventHandler, Dispatcher> collectionChangedHandlers;
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add
{
Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread); // experimental (can return null)...
collectionChangedHandlers.Add(value, dispatcher);
}
remove
{
collectionChangedHandlers.Remove(value);
}
}
#endregion

Now, whenever any changes occur to the collection it will trigger it from the relevant thread (the UI thread in our scenario).
private void internalOC_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
KeyValuePair<NotifyCollectionChangedEventHandler, Dispatcher>[] handlers = collectionChangedHandlers.ToArray();
if (handlers.Length > 0)
{
foreach (KeyValuePair<NotifyCollectionChangedEventHandler, Dispatcher> kvp in handlers)
{
if (kvp.Value == null)
{
kvp.Key(this, e);
}
else
{
kvp.Value.Invoke(kvp.Key, DispatcherPriority.DataBind, this, e);
}
}
}
}
}

Here's how you would use the NotifyCollectionChanged class :
private ObservableCollection<string> items;
private NotifyCollectionChangedWrapper<string> ItemsWrapper;
public void Test()
{
// create your ObservableCollection as you normally would
items = new ObservableCollection<string>();
// Now wrap it up to make it thread-safe (I'm using that term loosely here)
ItemsWrapper = new NotifyCollectionChangedWrapper<string>(items);
// Bind it to your UI control and you're done!
MyListView.ItemsSource = ItemsWrapper;
}

This attachment is hidden for guests. Please log in or register to see it.
Attachments:
  • Attachment This attachment is hidden for guests. Please log in or register to see it.
  • mnjon
  • OFFLINE
  • CE Staff
  • Posts: 78
  • Karma: 40
CodersEngine
  • Page:
  • 1
Moderators: mnjon
Time to create page: 0.44 seconds