Threadsafe ObservableCollection

When you are developing applications for WPF, Silverlight or WindowsPhone you often use ObservableCollection<T> to bind your data to the user interface. The limitation of ObservableCollection<T> is that it will only allow updates from the dispatcher thread. So if you want to insert data into the observable collection from another thread you need to write boiler plate code to marshal the changes back on the UI thread via the dispatcher. I wrote an observable collection which solves this issue internally.

Update: Switched from BeginInvoke to Invoke

    [DebuggerDisplay("Count = {Count}")]
    [ComVisible(false)]
    public class SafeObservableCollection : ObservableCollection
    {
        private readonly Dispatcher dispatcher;

        public SafeObservableCollection()
            : this(Enumerable.Empty())
        {
        }

        public SafeObservableCollection(Dispatcher dispatcher)
            : this(Enumerable.Empty(), dispatcher)
        {
        }

        public SafeObservableCollection(IEnumerable collection)
            : this(collection, Dispatcher.CurrentDispatcher)
        {
        }

        public SafeObservableCollection(IEnumerable collection, Dispatcher dispatcher)
        {
            this.dispatcher = dispatcher;

            foreach (T item in collection)
            {
                this.Add(item);
            }
        }

        protected override void SetItem(int index, T item)
        {
            this.ExecuteOrInvoke(() => this.SetItemBase(index, item));
        }

        protected override void MoveItem(int oldIndex, int newIndex)
        {
            this.ExecuteOrInvoke(() => this.MoveItemBase(oldIndex, newIndex));
        }

        protected override void ClearItems()
        {
            this.ExecuteOrInvoke(this.ClearItemsBase);
        }

        protected override void InsertItem(int index, T item)
        {
            this.ExecuteOrInvoke(() => this.InsertItemBase(index, item));
        }

        protected override void RemoveItem(int index)
        {
            this.ExecuteOrInvoke(() => this.RemoveItemBase(index));
        }

        private void RemoveItemBase(int index)
        {
            base.RemoveItem(index);
        }

        private void InsertItemBase(int index, T item)
        {
            base.InsertItem(index, item);
        }

        private void ClearItemsBase()
        {
            base.ClearItems();
        }

        private void MoveItemBase(int oldIndex, int newIndex)
        {
            base.MoveItem(oldIndex, newIndex);
        }

        private void SetItemBase(int index, T item)
        {
            base.SetItem(index, item);
        }

        private void ExecuteOrInvoke(Action action)
        {
            if (this.dispatcher.CheckAccess())
            {
                action();
            }
            else
            {
                this.dispatcher.Invoke(action);
            }
        }
    }

I used a slight variation of Deans test program to test my implementation. I did multiple runs without having any issues. You can also get the source from github.com here.Happy multi threading!

About the author

Daniel Marbach

22 comments

  • Hello,

    Simple (stupid?) question: what is the goal of the private methods ‘xxxxBase’; why do not call directly the base.xxxx methods inside the lambda of the overrided methods?

    thanks’

  • Hy
    You are right! I overlooked that! I just checked with the dispatcher documentation and it clearly states what are referring to:

    If multiple BeginInvoke calls are made at the same DispatcherPriority, they will be executed in the order the calls were made.

    So the calls altough coming from multiple threads are executed in the same order and one after another. So actually no need to lock the collection access. Thoughts?

  • Even if things weren’t ordered, I’m not sure I’d see a use for locking. Locking won’t give you any kind of gaurantees on ordering since the adding isn’t done inside the lock. You lock, call BeginInvoke, and immediately unlock while the adding is done asynchronously on the UI thread (oversimplifying, as you might already be on the UI in which case this is all synchronous, but why confuse the converstation ;). You could “fix” this by using Invoke instead of BeginInvoke, but that is a very bad idea that could actually lead to deadlock. So if the Dispatcher didn’t give any gaurantees on ordering even though you locked there’d still be no gaurantees on ordering. Not to mention there’s no gaurantees on the ordering of threads, so if you’re adding to a collection from multiple threads there’d better not be any relevance to the ordering of the items in the first place.

  • Sorry I started on the wrong foot. I didn’t want to refer to the actual ordering the items are going. I wanted to go into the way how the threads are “ordered” or serialized to access the collection. What I actually wanted to say is that the dispatcher guarantees that only one thread is accessing the observable collection and not multiple at the same time. Correct?

  • Yes, the Dispatcher is used to “dispatch” methods onto a single thread (conceptually the “UI” thread, but a Dispatcher can be associated with any thread, and for that matter an application technically can have multiple “UI” threads). So the code in the delegate passed to Invoke/BeginInvoke is executed on a single thread, which means no further synchronization is necessary.

    BTW, this solution won’t help you if the collection is bound to controls associated with different threads, though that’s a corner case you’re unlikely to run into.

  • Dim taskList As ObservableCollection(Of v2_Customer) = New ObservableCollection(Of v2_Customer)
    ” Dim custID As Guid = (CType(V2_CustomerDataGrid.SelectedItem, _
    ” v2_Customer)).Cust_UUID
    ” Generate some task data and add it to the task list.
    For index = 0 To taskList.Count – 1
    taskList.Add(New v2_Customer() With _
    {.Cust_UUID = custID, .Billing_Zip = .Billing_Zip
    })
    Next

    Dim taskListView As New PagedCollectionView(taskList)
    Me.CustomerDataForm1.ItemsSource = taskListView

  • I think you should check the collection passed in to the (4th) constructor for null, by wrapping the foreach with “if (collection != null)”. It would be easy enough for a programmer to accidentally pass in a null collection. With that change, you could also then replace the “Enumerable.Empty()” code in the other constructors with just “null”.

  • Another point – you should really add “{T}” to the class and base class to indicate that they are generic, since HTML is eating the angle brackets.

    Also, with VS2010, the warning CS1911 is gone, so you can indeed call the base class methods directly in the lambdas as the other poster asked.

  • It turns out there is a HUGE problem with this implementation. The problem is caused by the use of BeginInvoke (since it queues operations) in combination with the fact that most of the overridden methods use an ‘index’. This results in that index often reflecting an out-of-date value if you’re adding items on a background thread (Add isn’t virtual and delegates to InsertItem, which uses an index), since Add operations will be queued and not carried out yet. This will result in the nasty problem of some add and remove item operations occurring at the wrong index! The only fix seems to be to use Invoke() instead of BeginInvoke(), although it was certainly a nice idea to queue the operations to the UI thread… however, it results in HIGHLY unsafe code.

  • Hy ken,

    From the Framework Design Guidelines 2nd Edition (pg. 256):

    DO NOT return null values from collection properties or from methods returning collections. Return an empty collection or an empty array instead.

    I safely assumed this would also be the case for clients of the constructor.

    Daniel

  • Hi

    @Daniel Marbach

    Thanks, that’s nice. 🙂

    A remark: the code does not compile for Silverlight (due to Dispatcher.CurrentDispatcher and dispatcher.Invoke).
    How can I adapt it ?

    Remark 2: I suggest to update the git site (it has BeginInoke yet).

  • @Chris

    After some research, I found equivalents for Silverlight, but I can’t try them now:
    Application.Current.RootVisual.Dispatcher
    and
    this.dispatcher.BeginInvoke(..)

    I’m not sure they are so safe in that context.. but at least they compile.

  • Code still not safe in my opionen: “Index must be within the bounds of the List.” Exception
    After clear & add (index is not updated)

Recent Posts