Posts
47
Comments
79
Trackbacks
0
Have worker thread update ObservableCollection that is bound to a ListCollectionView

While playing around with WPF, I tried to do some multithreading where I have a worker thread updating my ObservableCollection, while having a ListCollectionView of that ObservableCollection being shown on a ListBox.

It was surprising to see that I get a NotSupportedException thrown, with the message saying 'This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.'.  That doesn't seem to make sense - In my mind, I understand how the thread that created the UI should be the one that handles all UI updates.  However, the data itself should be able to reside anywhere, and I should be able to update it however and whenever I want.

Looking for a solution, I created a class deriving from ObservableCollection thinking that I would just manually walk through the event's invocation list.  Well, that didn't quite work, since events are not accessible (other than for adding/removing delegates) to child classes.  Looking at the documentation, the CollectionChanged event in ObservableCollection is virtual - that means I can override it! Yeah!  I am glad someone at Microsoft decided to make that virtual.

So here's the code that I created - the pain is I now have to rename all occurrences of ObservableCollection to this new class.  Oh well, at least making it work with threads isn't too painful.

protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

{

   // Be nice - use BlockReentrancy like MSDN said

   using (BlockReentrancy())

   {

      System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;

      if (eventHandler == null)

         return;

 

      Delegate[] delegates = eventHandler.GetInvocationList();

      // Walk thru invocation list

      foreach (System.Collections.Specialized.NotifyCollectionChangedEventHandler handler in delegates)

      {

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

      }

   }

}

 

 Update:

There is a bug with the code where subscribers in the same thread won't get called - the code above has been updated with the fix.

 

It also uses CheckAccess (available, but not shown via Intellisense as mentioned here).

posted on Wednesday, January 16, 2008 6:30 PM Print
Comments
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Kevin Kerr
1/23/2008 10:30 AM
Nice job! This has been attempted several times by others. In case you are interested, here are some links to others:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2617169&SiteID=1

and here is a link my my solution:
http://blog.quantumbitdesigns.com/2008/01/06/wpf-cross-thread-collection-binding-part-4-the-grand-solution/

Keep up the good work!
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Jake
5/12/2008 5:06 PM
I'm trying the code you posted and I'm getting the following error.

Code:
System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;

Compile Error:
The event 'System.Collections.ObjectModel.ObservableCollection<T>.CollectionChanged' can only appear on the left hand side of += or -=

Any ideas?
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
SeveQ
9/21/2008 5:39 AM
Same issue here... Anything new about it?
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Muljadi Budiman
9/22/2008 8:48 AM
The event has to be overridden first (4th paragraph). You can override it by:
public override NotifyCollectionChangedEventHandler CollectionChanged;
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Muljadi Budiman
9/24/2008 9:13 AM
Should've had the event keyword there:
public override event NotifyCollectionChangedEventHandler CollectionChanged;
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
SeveQ
9/24/2008 10:20 AM
Okay, thank you. I've already found something thet helps me with this problem. It's very similar to your solution.
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Sabrina
12/16/2008 12:17 PM
Good job, been looking around for solution and this works for my problem very well.Most of solution requires the Dispatcher, and I dont want/have access to Dispacther as the collection is deep in the business layer. Getting Dispatcher from the handler is just brilliant!
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
DeanS
4/7/2009 6:30 PM
Well done. Walking the InvocationList is genius.
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
derm2
6/11/2009 1:51 PM
This solution has an issue, but it's only really apparent with multicore processors and large collections. I got this 5% of the time with a list of 4000.

Wpf code may throw indexoutofranceexceptions when removing items asynchronously. External code may try to access ony of the last items after they were removed from the collection, but before the invoke happens. Raising the priority to 'Send' didn't solve this either. The async code was still sitting in invoke when the exception was thrown.

I'll try to go the hard way and get most of the collection's functions invoke themselves when not on the UI thread.
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Sven
8/12/2009 4:28 AM
The comment "I don't want/have access to Dispatcher as the collection is deep in the business layer" is a bit misleading. In WPF, all objects have access to Dispatcher. The trick is to get the one for the main thread - I get a reference to the current Dispatcher in my constructor, which (in my specific example) is always being called on the main thread. I've been very successful doing what derm2 suggests, and invoking as appropriate.
Gravatar
# Comment for derm2
Muljadi Budiman
8/14/2009 5:38 AM
Unfortunately external code trying to access some of them items after they're removed is a general problem with multi-threading. A resource that can be accessed by multiple threads need to have locks before it is accessed.

Consider a single list (regular one, not even ObservableCollection) and code that will add an item to the list if the list doesn't contain the item. If the code is then run in parallel on multiple threads (on multi-core CPUs) but the code doesn't lock the list, it will have multiple same items added to the list.

You're correct that Dispatcher adds a level of complexity since the handler execution is not direct (it is added to the Dispatcher Queue); however to depend on the event not firing to assume an item hasn't been removed sounds dangerous too.
Gravatar
# re: Have worker thread update ObservableCollection that is bound to a ListCollectionView
Vijay
8/24/2009 5:31 AM
It is a smart solution. Helped me on time. Thank you very much.

Post Comment

Title *
Name *
Email
Url
Comment *  
 
News