Mike
Mike

Reputation: 3284

Why is CollectionChanged not threadsafe?

I'm working on a WPF application and found that property changed notifications on binded properties can happen from a background thread, however for making any change to an observablecollection(like adding or removing item) has to happen from a UI thread. My question is why is it so? Both INotifyPropertyChanged and INotifyCollectionChanged are subscribed by UI controls, then why an exception for INotifyPropertyChanged?

For example:

 public class ViewModel : INotifyPropertyChanged
    {
        ObservableCollection<Item> _items = new ObservableCollection<Item>();

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                //Can fire this from a background thread without any crash and my 
                //Name gets updated in the UI
                InvokePropertyChanged(new PropertyChangedEventArgs("Name"));
            }
        }

        public void Add(Item item)
        {
            //Cant do this from a background thread and has to marshal.
            _items.Add(item);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void InvokePropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, e);
        }
    }

NOTE: CollectionChanged event from a background thread crashes the app, however PropertyChanged event from a background thread updates the UI without any issues and yes, this is in .NET 4.0

Upvotes: 2

Views: 1558

Answers (2)

EldHasp
EldHasp

Reputation: 7908

Most likely you mean the binding mechanisms.

Binding does not work directly with the CLR property and the PropertyChanged event.
Binding uses reflection for its work. These are in order of priority: PropertyDescriptor, PropertyInfo, DependencyProperty, and DynamicPropertyAccessor.
You can see it here: PropertyPathWorker.SetValue(object item, object value).
For the same reason, if a property changes ONLY by bindings, then it will be monitored regardless of the presence of the INotifyPropertyChanged interface.
Also, the use of reflection allows you not to worry in which flow the observed property will be changed.

In the case of collections, binding is also insensitive to the stream in which this collection will be assigned to the monitored property.
BUT monitoring of changes in the collection (INotifyCollectionChanged and IBindingList) is no longer provided by the binding mechanism, but by the internal logic of the ItemsControl class.
There is already a direct subscription to observation events, this makes this logic sensitive to the thread in which the collection will change. If it is not a UI thread, then the observation will be destroyed, or even an exception will be thrown.

It is enough just to be convinced of this.

Demo example. Class ViewModel with random collection change.

public class RandomCollectionViewModel
{
    public ObservableCollection<int> Numbers { get; }
        = new ObservableCollection<int>() { 1, 2, 3 };
    private static readonly Random random = new Random();
    private readonly Timer timer = new Timer();

    public RandomCollectionViewModel()
    {
        timer.Interval = 500;
        timer.Elapsed += (s, e)
            => Numbers[random.Next(Numbers.Count)] = random.Next();
        timer.Start();
    }
}

XAML using bindings to display collection items:

<StackPanel>
    <FrameworkElement.DataContext>
        <local:RandomCollectionViewModel/>
    </FrameworkElement.DataContext>
    <TextBlock Text="{Binding Numbers[0]}"/>
    <TextBlock Text="{Binding Numbers[1]}"/>
    <TextBlock Text="{Binding Numbers[2]}"/>
</StackPanel>

XAML using ItemsControl to display collection items:

<StackPanel>
    <FrameworkElement.DataContext>
        <local:RandomCollectionViewModel/>
    </FrameworkElement.DataContext>
    <ItemsControl ItemsSource="{Binding Numbers}"/>
</StackPanel>

These examples clearly show the difference in the work of bindings and ItemsContol.

Upvotes: 1

Jon
Jon

Reputation: 437336

This problem is not related to thread safety. The issue is that the CollectionChanged event is raised from a worker thread, which means the handlers are executed in that same thread, and when a handler tries to touch the UI you have an exception because that's only allowed from the UI thread.

The same would also happen for the PropertyChanged event if the situation were identical, there is no special treatment being given to either event.

If you need to touch the UI from within your event handlers then you have to either make sure the event is raised on the UI thread or else the event handlers have to check with Dispatcher.CheckAccess if marshalling changes to the UI thread is needed and Dispatcher.BeginInvoke to do so.

Upvotes: 5

Related Questions