Handling Cross-Threading in ObservableCollection


One of the best things I reckon in WPF is the introduction of ObservableCollection<T>. With this class, databinding becomes a breeze and developers no longer need to write custom code to get the same effect with INotifyPropertyChanged.

I was playing with some multi-threading code today to update my ObservableCollection and ran into an Exception with this message “This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.” Woah, what have I done? 🙂

It’s pretty weird to get a Cross Thread Exception from a Collection, but if you think about it, there’s some sense to it. An ObservableCollection is typically used for WPF Databinding, and internally raises a CollectionChanged event when it’s contents are modified. If an item is added on a different thread, that also means that CollectionChanged event is raised on that different thread and WPF’s databinding controls will attempt to refresh based on that change. We all know that cross-threading for UI controls is a BIG NO-NO, and a control can only be accessed on the same thread that created it, namely the UI Thread. Can you see the linkage now? To overcome this, we only need to marshal the update logic on the ObservableCollection back to the right thread or Dispatcher in WPF terms.

Here’s a snippet of code I wrote to do the marshaling. This class will grab a reference to the current Dispatcher (current Thread) in it’s constructor, so you will need to ensure that you create your ObservableCollection on the same Thread. This is generally not a problem, unless you create your ObservableCollection on a different Thread.

public class ObservableCollectionMarshaler
{
    public Dispatcher Dispatcher { get; set; }

    public ObservableCollectionMarshaler()
    {
        Dispatcher = Dispatcher.CurrentDispatcher;
    }

    public void AddItem<t>(ObservableCollection</t><t> oc, T item)
    {
        if (Dispatcher.CheckAccess())
        {
            oc.Add(item);
        }
        else
        {
            Dispatcher.Invoke(new Action</t><t>(t => oc.Add(t)), DispatcherPriority.DataBind, item);
        }
    }

    public void RemoveItem</t><t>(ObservableCollection</t><t> oc, T item)
    {
        if (Dispatcher.CheckAccess())
        {
            oc.Remove(item);
        }
        else
        {
            Dispatcher.Invoke(new Action</t><t>(t => oc.Remove(t)), DispatcherPriority.DataBind, item);
        }
    }

    public void Clear</t><t>(ObservableCollection</t><t> oc)
    {
        if (Dispatcher.CheckAccess())
        {
            oc.Clear();
        }
        else
        {
            Dispatcher.Invoke(new Action(() => oc.Clear()), DispatcherPriority.DataBind);
        }
    }
}

If grabbing Dispatcher.CurrentDispatcher in the constructor is too unsafe for you, you can always pass in a Dispatcher in your constructor parameter.

If you are going to do lots of asynchronous processing, might be a good idea to create a Wrapper class for an ObservableCollection to handle the marshaling logic. Hope this has been helpful.

Share this post:

Advertisements
Posted in WPF. Tags: . 3 Comments »

3 Responses to “Handling Cross-Threading in ObservableCollection”

  1. David Angel Says:

    This may seem like a good idea but…
    When loading data into the collection on a worker thread.
    You may need to use the data right after calling any of these methods.
    Disbatcher will run in an async manner so after calling these the colleciton will be in an unknown state.

  2. Anthony Says:

    Try this collection which takes care of this problem as well as other multi-threaded problems that will inevitably crop up with other approaches : http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: