[WPF] Binding to an asynchronous collection


As you may have noticed, it is not possible to modify the contents of an ObservableCollection on a separate thread if a view is bound to this collection : the CollectionView raises a NotSupportedException :

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

To illustrate this, let’s take a simple example : a ListBox bound to a collection of strings in the ViewModel :

        private ObservableCollection<string> _strings = new ObservableCollection<string>();
        public ObservableCollection<string> Strings
        {
            get { return _strings; }
            set
            {
                _strings = value;
                OnPropertyChanged("Strings");
            }
        }
    <ListBox ItemsSource="{Binding Strings}"/>

If we add items to this collection out of the main thread, we get the exception mentioned above. A possible solution would be to create a new collection, and assign it to the Strings property when it is filled, but in this case the UI won’t reflect progress : all items will appear in the ListBox at the same time after the collection is filled, instead of appearing as they are added to the collection. It can be annoying in some cases : for instance, if the ListBox is used to display search results, the user expects to see the results as they are found, like in Windows Search.

A simple way to achieve the desired behavior is to inherit ObservableCollection and override OnCollectionChanged and OnPropertyChanged so that the events are raised on the main thread (actually, the thread that created the collection). The AsyncOperation class is perfectly suited for this need : it allows to “post” a method call on the thread that created it. It is used, for instance, in the BackgroundWorker component, and in many asynchronous methods in the framework (PictureBox.LoadAsync, WebClient.DownloadAsync, etc…).

So, here’s the code of an AsyncObservableCollection class, that can be modified from any thread, and still notify the UI when it is modified :

    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        private AsyncOperation asyncOp = null;

        public AsyncObservableCollection()
        {
            CreateAsyncOp();
        }

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

        private void CreateAsyncOp()
        {
            // Create the AsyncOperation to post events on the creator thread
            asyncOp = AsyncOperationManager.CreateOperation(null);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            // Post the CollectionChanged event on the creator thread
            asyncOp.Post(RaiseCollectionChanged, e);
        }

        private void RaiseCollectionChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
           base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            // Post the PropertyChanged event on the creator thread
            asyncOp.Post(RaisePropertyChanged, e);
        }

        private void RaisePropertyChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
            base.OnPropertyChanged((PropertyChangedEventArgs)param);
        }
    }

The only constraint when using this class is that instances of the collection must be created on the UI thread, so that events are raised on that thread.

In the previous example, the only thing to change to make the collection modifiable across threads is the instantiation of the collection in the ViewModel :

private ObservableCollection<string> _strings = new AsyncObservableCollection<string>();

The ListBox can now reflect in real-time the changes made on the collection.

Enjoy ;)

Update : I just found a bug in my implementation : in some cases, using Post to raise the event when the collection is modified from the main thread can cause unpredictable behavior. In that case, the event should of course be raised directly on the main thread, after checking that the current SynchronizationContext is the one in which the collection was created. This also made me realize that the AsyncOperation actually doesn’t bring any benefit : we can use the SynchronizationContext directly instead. So here’s the new implementation :

    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)
            {
                // Execute the CollectionChanged event on the current thread
                RaiseCollectionChanged(e);
            }
            else
            {
                // Post the CollectionChanged event on the creator thread
                _synchronizationContext.Post(RaiseCollectionChanged, e);
            }
        }

        private void RaiseCollectionChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
            base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (SynchronizationContext.Current == _synchronizationContext)
            {
                // Execute the PropertyChanged event on the current thread
                RaisePropertyChanged(e);
            }
            else
            {
                // Post the PropertyChanged event on the creator thread
                _synchronizationContext.Post(RaisePropertyChanged, e);
            }
        }

        private void RaisePropertyChanged(object param)
        {
            // We are in the creator thread, call the base implementation directly
            base.OnPropertyChanged((PropertyChangedEventArgs)param);
        }
    }
About these ads

18 Responses to “[WPF] Binding to an asynchronous collection”

  1. Robinson Says:

    Just what I needed. Thanks Thomas.

  2. WeSam Abdallah Says:

    Great article. This one of few good solutions I found online. Keeps us updated with modifications to the class. MS should have included this in the framework.

  3. Ryan Says:

    Awesome post!

    This is one of the easier (and better) work-arounds I have seen. I have actively been trying to use the MVVM pattern in any wpf app I create but have become extremely frustrated at times while using multi-threading. This solves all of my problems!

    Thanks!

  4. John Says:

    Thank you!
    It is the worst limitation of WPF i’ve encountered.

  5. Scott Kuehn Says:

    I extracted the functionality so that it could be used as a base class or be used externally via an interface. The interface allows me to code a global dispatcher that will always make call on the creator thread.

    	public interface IAsyncContext
    	{
    		/// 
    		/// Get the context of the creator thread
    		/// 
    		SynchronizationContext AsynchronizationContext { get; }
    
    		/// 
    		/// Test if the current executing thread is the creator thread
    		/// 
    		bool IsAsyncCreatorThread { get; }
    
    		/// 
    		/// Post a call to the specified method on the creator thread
    		/// 
    		/// Method that is to be called
    		/// Method parameter/state
    		void AsyncPost(SendOrPostCallback callback, object state);
    	}
    
    	public class AsyncContext : IAsyncContext
    	{
    		private readonly SynchronizationContext _asynchronizationContext;
    
    		/// 
    		/// Constructor - Save the context of the creator/current thread
    		/// 
    		public AsyncContext()
    		{
    			_asynchronizationContext = SynchronizationContext.Current;
    		}
    
    		/// 
    		/// Get the context of the creator thread
    		/// 
    		public SynchronizationContext AsynchronizationContext
    		{
    			get { return _asynchronizationContext; }
    		}
    
    		/// 
    		/// Test if the current executing thread is the creator thread
    		/// 
    		public bool IsAsyncCreatorThread
    		{
    			get { return SynchronizationContext.Current == AsynchronizationContext; }
    		}
    
    		/// 
    		/// Post a call to the specified method on the creator thread
    		/// 
    		/// Method that is to be called
    		/// Method parameter/state
    		public void AsyncPost(SendOrPostCallback callback, object state)
    		{
    			if (IsAsyncCreatorThread)
    				callback(state); // Call the method directly
    			else
    				AsynchronizationContext.Post(callback, state);  // Post on creator thread
    		}
    	}
    
    	public class AsyncObservableCollection : ObservableCollection, IAsyncContext
    	{
    		private readonly AsyncContext _asyncContext = new AsyncContext();
    
    		#region IAsyncContext Members
    		public SynchronizationContext AsynchronizationContext { get { return _asyncContext.AsynchronizationContext; } }
    		public bool IsAsyncCreatorThread { get { return _asyncContext.IsAsyncCreatorThread; } }
    		public void AsyncPost(SendOrPostCallback callback, object state) { _asyncContext.AsyncPost(callback, state); }
    		#endregion
    
    		public AsyncObservableCollection() { }
    
    		public AsyncObservableCollection(IEnumerable list) : base(list) {}
    
    		protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    		{
    			AsyncPost(RaiseCollectionChanged, e);
    		}
    
    		private void RaiseCollectionChanged(object param)
    		{
    			base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
    		}
    
    		protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    		{
    			AsyncPost(RaisePropertyChanged, e);
    		}
    
    		private void RaisePropertyChanged(object param)
    		{
    			base.OnPropertyChanged((PropertyChangedEventArgs)param);
    		}
    	}
    
    • yk Says:

      Not working for me.
      After I created a new class with above code and changed my code for new instance of ObservableCollection, I got same error. no clue……
      This is snapshot of my code:

      if (_MyLanguages == null) _MyLanguages = new AsyncObservableCollection();
      if (_SelectedLanguage.Length > 0 && !_MyLanguages.Contains(_SelectedLanguage))
      {
      MyLanguages.Add(_SelectedLanguage);
      OnPropertyChanged(“MyLanguages”);
      OnPropertyChanged(“SelectedMyLanguage”);
      }
      I tried both of your classes above, but not working for me.
      any idea?

      Thanks in advance,

  6. yk Says:

    BTW, this is a ViewModel and Xaml includes ComboBox instead Listbox.

  7. mik2 Says:

    Doesnt work for me either. _synchronisaztionContext *always* seems to be null in OnPropertyChanged. Do you have a working example of this?

    • Thomas Levesque Says:

      When do you create the collection? I think SynchronizationContext.Current is initialized when the first WPF window is loaded, so if you create the collection too early, it will be null. But that’s just a possibility, I’m not sure what’s causing the problem in you case

  8. Obberer Says:

    Billion thanks to you!

  9. Tom Says:

    For Mik2 :
    http://blogs.msdn.com/b/kaelr/archive/2007/09/05/synchronizationcallback.aspx

    (at least it may help someone else in the future :p )

  10. Diana Says:

    Sometimes i get the error that the collection don´t support changes to a thread different from the dispatcher thread at this line: base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param); in the RaiseCollectionChanged funktion. But my collections is the asyncobservablecollection, why does this happen then?

    • Thomas Levesque Says:

      Hi Diana,
      On which thread did you create the collection? It has to be created on the dispatcher thread (i.e. the UI thread), so that the notifications are raised on that thread

      • Diana Says:

        I create it on the main thread i think. It works most of the time but for one collection i sometimes get this error.

  11. Andy McDonald Says:

    Thanks very much for this – very simple to implement and makes a huge difference!

  12. Dries Hoebeke Says:

    Thanks for posting this! Nice solution.


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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: