C#, MultiThreading, wpf

Modifying ObservableCollection from worker threads in WPF

If you have worked with WPF and used DataBinding you would know that if you want to update the UI when an object changes other than from the binding, you have to implement INotifyPropertyChanged Interface (Or use Dependency properties) and if you are working with a collection, then the best bet is to use an Observable collection which makes updates to the UI when the underlying collection is modified (Add, Delete etc).

PROBLEM

All works well until you are working with Threads and your worker threads try to modify the ObservableCollection.

You would see an exception like this


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

SOLUTION

If you are using WPF 4.5  then all you have to do is this:

  1. Create the ObservableCollection in the main thread (usually done in the ViewModel)
  2. Use BindingOperations.EnableCollectionSynchronization(YourObservableCollection, A class level lock)
    BindingOperations.EnableCollectionSynchronization(EnvironmentErrors, _lock);

That is it, now you can update the ObservableCollection from worker threads and see the UI being updated at the same time.

If you aren’t using WPF 4.5 then you can use this AsynchronousObservableCollection class to update the collection from non-ui threads.

 public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        private SynchronizationContext synchronizationContext = SynchronizationContext.Current;

        public AsyncObservableCollection()
        {
        }

        public AsyncObservableCollection(IEnumerable<T> list)
            : base(list)
        {
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (SynchronizationContext.Current == synchronizationContext)
            {
                RaiseCollectionChanged(e);
            }
            else
            {
                synchronizationContext.Send(RaiseCollectionChanged, e);
            }
        }

        private void RaiseCollectionChanged(object param)
        {
            base.OnCollectionChanged((NotifyCollectionChangedEventArgs) param);
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (SynchronizationContext.Current == synchronizationContext)
            {
                RaisePropertyChanged(e);
            }
            else
            {
                synchronizationContext.Send(RaisePropertyChanged, e);
            }
        }

        private void RaisePropertyChanged(object param)
        {
            base.OnPropertyChanged((PropertyChangedEventArgs) param);
        }
    }
Advertisements
Standard