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)