Search code examples
c#wpfmultithreadingobservablecollection

Threading and creating ObservableCollection


I would like some explanation on how C# (WPF) works behind the code. Situation (clearly hypothetical for the sake of simplicity):

On our UI thread, we create the Settings object. It has a variable:

public ObservableCollection<Something> collection;

From a different background thread, we call

try{
    Settings.Modify()
}

which then does the following:

Settings.Modify()
{
    collection = new ObservableCollection<Something>();
    collection.Add(...)
}

Now, of course this throws an exception at the Add function:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

But the collection = new ObservableCollection(); line executed. As far as I know, this new collection was created on the stack of the background thread. The WPF UI binds to this variable:

<ItemsControl ItemsSource="{Binding Settings.collection}"

In my understanding, the UI thread should not be able to access this collection anymore. What would happen if it tried, could it block the UI thread for some reason?

Oher question: Why did the compiler let me replace an object which clearly belongs on the UI thread with an other on the background thread, even though it knows I'm messing up when I tried to Add to that collection from another thread.

EDIT: This problem's solution is already answered here:

What I am still curious about this: If the

collection = new ObservableCollection<Something>();

function is called alone, what would the UI thread do if it tried to access this collection?


Solution

  • The problem isn't actually the ObservableCollection<Something> at all, it's the <ItemsControl ItemsSource="{Binding Settings.collection}".

    ObservableCollection itself doesn't have any problem being accessed across multiple threads (as long as those threads are accessing it one at a time). The same goes for normal (non-DependencyProperty) properties- they don't inheritly belond to a single thread. The WPF binding system can (for the most part) handle changes to bound properties from other threads (provided you aren't using DependencyProperty). What can't handle changes from other threads, is ItemsControl.

    Let's step through it, but first I have to make a few assumptions. First, I have to assume that collection is a actually a property, not a field (or is at least exposed via a property), since you can't bind to a field. Second, I'll have to assume that the INotifyPropertyChanged interface is involved, probably implemented on the Settings class.

    So stepping through:

    1. In Settings.Modify, you execute collection = new ObservableCollection<Something>();. This successfully updates the property. The WPF binding system gets notified of this change (through the presumed INotifyPropertyChanged interface) and handles the thread change for you. So ItemsControl.ItemsSource gets updated on the UI thread, via the binding, to the new value of collection.
    2. ItemsControl takes this new ObservableCollection and creates a CollectionView which it uses as the data source for its items.
    3. Back in Settings.Modify, you call collection.Add(...). This successfully adds an item to the ObservableCollection<Something>, which triggers its INotifyCollectionChanged.CollectionChanged event.
    4. The CollectionView which the ItemsControl created, handles the CollectionChanged event, but still on the other thread, where it was raised. The CollectionView, as the exception says, doesn't "support changes to its SourceCollection from a thread different from the Dispatcher thread". So it throws an exception. This exception looks like it's coming from collection.Add, but if you look in the call stack, you'll see it actually comes from many frames deeper. collection.Add is just the deepest level of your code that is involved.

    When working with ObservableCollections and multiple threads, I would advise creating the full collection on the background thread (if possible), before passing the full collection back to the UI thread for binding. In your example, you could create a local variable, add your items to that, then set it to your collection property once all the items are in place. Alternatively, you could pass individual items back to the UI thread to be added, using Dispatcher.Invoke or a Task<Something>.