[WPF] Binding to an asynchronous collection

Very poorPoorAverageGoodExcellent (18 votes) 
Loading...Loading...

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
            {
                // Raises the CollectionChanged event on the creator thread
                _synchronizationContext.Send(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
            {
                // Raises the PropertyChanged event on the creator thread
                _synchronizationContext.Send(RaisePropertyChanged, e);
            }
        }

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

Update: changed the code to use Send instead of Post. Using Post caused the event to be raised asynchronously on the UI thread, which could cause a race condition if the collection was modified again before the previous event was handled.

58 Comments

  1. Robinson says:

    Just what I needed. Thanks Thomas.

  2. 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?

    • 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?

    • 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. Thanks for posting this! Nice solution.

  13. José Martins says:

    Very useful, thank you very much!

  14. Vijay says:

    If I subscribe to the CollectionChanged event of this derived collection, I am not getting the event handler”s breakpoint hit when I add/remove from the collection. Does your collection need to be modified in some way to support subscribing to CollectionChanged event?

    • Thomas Levesque says:

      Hi Vijay,
      The breakpoint should be reached… the event is raised on the UI thread instead of the current thread, but it is raised nonetheless. Perhaps it has something to do with your debug settings ?

      • Vijay says:

        I am actually using an underlying collection of ICollection which is wrapped to make your AsyncObservableCollection object. Is that the reason that the CollectionChanged event on the wrapped AsyncCollectionChanged object is not received.
        Please suggest a solution in this scenario to get the event with the underlying collection as ICollection.

        • Thomas Levesque says:

          Are you adding the items to your underlying ICollection? It won”t work; ObservableCollection (and AsyncObservableCollection as well) doesn”t wrap the collection passed as a parameter, it makes a copy of it. You need to add items directly to the AsyncObservableCollection.

          • Vijay says:

            I put a breakpoint in AsyncObservableCollection.OnCollectionChanged() method and it does hit there and raises base.OnCollectionChanged(), not sure why it doesn”t hit the subscribed CollectionChanged event for the wrapped AsyncObservableCollection based property?
            Do you have any idea for this issue to be resolved properly?

          • Thomas Levesque says:

            Sorry, I have no idea what could be causing the issue…

  15. Tobias H. says:

    nice! thanks for this great solution

  16. M says:

    When I use the AsyncObservableCollection as a source to a CollectionViewSource on a ListBox, my list box ends up with copies of every item. The original collection still shows the correct number of items. Using the same code with a regular ObservableCollection works fine. It looks like the ListBox is getting notified twice and creating two UI containers?

    Also, if I manually add to or refresh the list, it appears to fix itself?

    • Thomas Levesque says:

      That”s weird… the events are still raised only once, so I don”t know why it”s doing that. If you can send a short but complete program that exhibits the issue, I”ll have a look at it.

      • M says:

        Hi,

        So, if you create a new project and just add a ”MyListBox” named Listbox in the default grid xaml and add the following code:

        CollectionViewSource cvs;

        public MainWindow()
        {
        InitializeComponent();

        ObservableCollection o = new AsyncObservableCollection();

        o.Add(“test”);
        o.Add(“test2″);
        o.Add(“test3″);

        cvs = new CollectionViewSource();
        cvs.Source = o;

        MyListBox.ItemsSource = cvs.View;

        new Task(() =>
        {
        o.Add(“test4″);
        }).Start();
        }

        private void MyListBox_PreviewMouseDown_1(object sender, MouseButtonEventArgs e)
        {
        cvs.View.Refresh();
        }

        You”ll see the listbox have 1 of the first 3 items, and a two test4s. If you attach with snoop or something, you”ll see the ListBox ItemSource count to still be 4, so it”s purely the listbox creating an extra container for the same item.

        If I add a PreviewMouseDown handler and refresh the view, it”ll fix itself. It”s a really weird issue.

        If you spawn a second task:

        new Task(() =>
        {
        o.Add(“test5″);
        }).Start();

        It”ll duplicate both the test4 and test5. However, if you add another item outside of a test (like just o.Add(“test5″)) it fixes itself again as if I had clicked (with my handler).

        If I use the regular ObservableCollection, everything works as expected, but I”ll run into the NotSupportedException when actually running this through my DataBinding.

        Thanks for the quick reply!

        • Thomas Levesque says:

          I reproduced the bug on my PC. I think it”s because when “test4″ is added from the task, the ListBox hasn”t finished loading the items yet (actually it”s a race condition). In this case, the sequence of events is as follows:

          • add “test”, “test2″, “test3″ (on UI thread)
          • add “test4″ (on worker thread). The item is added immediately, but the CollectionChanged event is posted to the UI thread to be handled later
          • before the CollectionChanged event is handled, the list loads the 4 items
          • then the CollectionChanged event is handled, which causes “test4″ to be added again

          So I”m not sure it”s really a bug in the collection itself, but rather in the way it”s used. Since the CollectionChanged event isn”t going to be handled immediately when an item is added, you should avoid adding items from another thread before the list has been loaded. If you do it in the Loaded event instead of the constructor, it works fine.

  17. Sumanth says:

    Awesome.. It worked like a charm.. Solved my issue. I was looking all around and this one was simple and solved my problem! Thanks for posting this Thomas!

  18. Agent 007 says:

    This just saved my life, thanks for sharing it and keep it up!

  19. Fei says:

    Hi Thomas,

    Thanks for the great implementation, but it has a flaw inside.

    Let’s say a worker thread is inserting items into the AsyncObservableCollection every 50 ms, (or even faster), and during the UI thread is handling the OnCollectionChanged() there is a UI delay (for example a new page of complicated UI is initialized and last for 500ms). The UI thread will not be able to handle the update but find the collection is already updated by worker thread and will raise exception of System.InvalidOperationException with Additional information of “Added item does not appear at given index”.
    I’m new to WPF, but I guess each Insert() operation should also be Post to UI thread.
    What’s your idea?

    • Thomas Levesque says:

      Hi Fei,
      I’m not sure I understand the scenario you’re describing… Do you have a repro so I can look into the problem?

  20. Jeff says:

    Hi,
    I am using your collection in a COM thread. I then write info the UI via some properties that is then display in ListBoxs, works fine.
    I need to do updates, do to so I call Clear(), with the intention of then updateing the displayed data. What happens though is an exception in thrown in, RaiseCollectionChanged(), says,
    ’1′ index in collection change event is not valid for collection of size ’0′.
    Not sure what to make of this.
    Thanks for any assitance.
    Jeff

    • Thomas Levesque says:

      Hi Jeff,
      Does it still happen if you use Send instead of Post?

      • Jeff says:

        Hi,
        Not sure which I am using.
        I call Clear() on the property below,

        private ObservableCollection shortTextActiveFaults = new AsyncObservableCollection();
        public ObservableCollection SelectedShortFault
        {
        get
        {
        return shortTextActiveFaults;
        }

        set
        {

        var shortFault = value;
        if (shortFault.Count > 0)
        {

        if (shortTextActiveFaults != null)
        {
        if (!shortTextActiveFaults.Contains(shortFault[0]))
        {
        shortTextActiveFaults.Add(shortFault[0]);
        }

        }

        }

        base.OnPropertyChanged(“SelectedShortFault”);
        }
        }

        • Thomas Levesque says:

          I mean, in the code of AsyncObservableCollection. Try to replace _synchronizationContext.Post with _synchronizationContext.Send.

          BTW, you’re not calling Clear anywhere in the code you posted…

          • Jeff says:

            Sorry,
            The thread I am reading in recieves 1 – many packets that drive the data update, continious stream, Sends an end packet, and then a new steam starts. So when I recieve that end packet, in the thread I call, SelectedShortFault.Clear();
            Then the crash occurs.

        • Thomas Levesque says:

          OK, but did you change the code as I told you? I fixed it in the post.

          • Jeff says:

            OK,
            You are correct.
            I replaced Post with Send, and no more crashes.
            Thanks a lot for the quick response and the
            code, helps my project alot.

      • Jeff says:

        Stepping through with the debugger I see it always goes through Post, works a number of times at at some point a crash. I do not notice anything different in the data when crashing or not, does say the index should be non-negative.

        • Thomas Levesque says:

          Yes, that’s because of a race condition. Post executes the delegate asynchronously, so it’s possible that the collection has already changed when the event is processed. That’s why I suggested to replace Post with Send (which executes the delegate synchronously)

          I will update my post to fix the code.

  21. José Martins says:

    Needed to add _synchronizationContext != null verifications for it to work on a specific case. Other than that it’s awesome!

  22. Valerio says:

    Hi, thank you for this implementation, i really love it! Just a little thing: If i delete an element in this AsyncObservableCollection and quickly add another one i get a “ItemsControl is incoerent with source data” error, so i can’t use for context where lot of data change quickly (or where CPU is really slow). Any idea on how to fix it?

    • Thomas Levesque says:

      Hi Valerio,
      Did you use the latest version? (see the update at the end of the post). Make sure you’re using Send rather than Post.

      • Valerio says:

        Hi, yes i’m using the latest version with “Send”! I declared the AsyncObservableCollection with this:
        public static AsyncObservableCollection reportList = new AsyncObservableCollection();

        and the problem is here, when count is >= 5:

        if (reportList.Count >= 5)
        {
        reportList.RemoveAt(0);
        }
        reportList.Add(report);

        • Thomas Levesque says:

          Can you reproduce the problem reliably ? I managed to reproduce it a few times, but it seems to happen randomly, which makes it hard to debug…

  23. Juan Pablo says:

    People like you make a better world. Very tks.

  24. James Kim says:

    Hi Thomas,

    Thanks for such a wonderful post. I made one observation during testing, which was that if I try to Clear() an empty AOC, I get an invalid index error in the RaiseCollectionChanged method. Any ideas would be appreciated!

  25. Aiden Caine says:

    Hi Thomas

    Unfortunately this solution isn’t ideal. Simply dispatching the PropertyChange and CollectionChanged events to the UI thread may get rid of the WPF exception but does not address the underlying problem – that ObservableCollection itself is not thread-safe (which is what that exception is trying to tell you).

    To safely use ObservableCollection across threads you need to either synchronize access to ensure threads don’t access it concurrently (which isn’t an option if you are binding to it in WPF), or you need to ensure that all reads\writes occur the same thread (in WPF’s case the UI thread).

    To put it another way: the problem wasn’t that the events where firing on an non-UI thread but rather that you were updating the collection itself from a non-UI thread.

    Your updated implementation does fix this issue by delegating the actual update operations rather than the events to the UI thread so I would recommend more clearly pointing people towards that. Although the code above will work fine most of the time, there is a potential for some real nasty bugs.

    Thanks

    Aiden

2 Trackbacks

Leave a comment

css.php