I've got an ObservableCollection<A> a_collection;
The collection contains 'n' items. Each item A looks like this:
public class A : INotifyPropertyChanged
{
public ObservableCollection<B> b_subcollection;
Thread m_worker;
}
Basically, it's all wired up to a WPF listview + a details view control which shows the b_subcollection
of the selected item in a separate listview (2-way bindings, updates on propertychanged etc.).
The problem showed up for me when I started to implement threading. The entire idea was to have the whole a_collection
use it's worker thread to "do work" and then update their respective b_subcollections
and have the gui show the results in real time.
When I tried it , I got an exception saying that only the Dispatcher thread can modify an ObservableCollection, and work came to a halt.
Can anyone explain the problem, and how to get around it?
Starting from .NET 4.5 there is a built-in mechanism to automatically synchronize access to the collection and dispatch CollectionChanged
events to the UI thread. To enable this feature you need to call BindingOperations.EnableCollectionSynchronization
from within your UI thread.
EnableCollectionSynchronization
does two things:
CollectionChanged
events on that thread.Very importantly, this does not take care of everything: to ensure thread-safe access to an inherently not thread-safe collection you have to cooperate with the framework by acquiring the same lock from your background threads when the collection is about to be modified.
Therefore the steps required for correct operation are:
This will determine which overload of EnableCollectionSynchronization
must be used. Most of the time a simple lock
statement will suffice so this overload is the standard choice, but if you are using some fancy synchronization mechanism there is also support for custom locks.
Depending on the chosen lock mechanism, call the appropriate overload on the UI thread. If using a standard lock
statement you need to provide the lock object as an argument. If using custom synchronization you need to provide a CollectionSynchronizationCallback
delegate and a context object (which can be null
). When invoked, this delegate must acquire your custom lock, invoke the Action
passed to it and release the lock before returning.
You must also lock the collection using the same mechanism when you are about to modify it yourself; do this with lock()
on the same lock object passed to EnableCollectionSynchronization
in the simple scenario, or with the same custom sync mechanism in the custom scenario.