DataBinding & Multi-Threading In WPF [Part Two]

|

In my last post, I talked about a solution to overcome the current WPF limitation to enable multi-threaded data binding.

But there are one of drawback which I introduced in that solution. In that version of BindingCollection, I just simply marshal the collection changed notification back to UI thread, this sounds innocuous, but sometimes, this is not what we really expect, imagine that I have a bunch of listeners listening to BindingCollection.CollectionChanged events, some of them are free-threaded objects, but some of them would be single-threaded, actually CollectionView is the primary listener to this event. free threaded listeners can run on any threads, they are not bound to UI threads, so we shouldn't force them to run in the UI threads, but single-threaded listeners(DispatcherObjects etc) are actually bound to UI threads, so we should let them running inside UI threads as we previously did, to put it simple, we should treat free-threaded listeners and single-threaded listeners differently, so here comes another refined implementation of this collection, this time I call it BindableCollection, because I find that the name of BindingCollection is a bit misleading indeed, It could imply "a collection of Binding" which apparently is not what I want to do with it.

using System;
using System.Threading;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Collections.Specialized;
using System.Collections.ObjectModel;

namespace Sheva.Windows.Data
{
    /// <summary>
    /// An ObservableCollection&lt;T&gt; enhanced with capability of free threading.
    /// </summary>
    [Serializable]
    public class BindableCollection<T> : ObservableCollection<T>
    {
        /// <summary>
        /// Initializes a new instance of the<see cref="BindingCollection&lt;T&gt;">BindingCollection</see>.
        /// </summary>
        public BindableCollection() : base() {}

        /// <summary>
        /// Initializes a new instance of the<see cref="BindingCollection&lt;T&gt;">BindingCollection</see>
        /// class that contains elements copied from the specified List&lt;T&gt;.
        /// </summary>
        /// <param name="list">The list from which the elements are copied.</param>
        /// <exception cref="System.ArgumentNullException">The list parameter cannot be null.</exception>
        public BindableCollection(List<T> list) : base(list) {}

        /// <summary>
        /// Initializes a new instance of the<see cref="BindingCollection&lt;T&gt;">BindingCollection</see>
        /// class that contains elements copied from the specified IEnumerable&lt;T&gt;.
        /// </summary>
        /// <param name="list">The list from which the elements are copied.</param>
        /// <exception cref="System.ArgumentNullException">The list parameter cannot be null.</exception>
        public BindableCollection(IEnumerable<T> list)
        {
            if (list == null) throw new ArgumentOutOfRangeException("The list parameter cannot be null.");
            foreach (T item in list)
            {
                this.Items.Add(item);
            }
        }

        /// <summary>
        /// Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
        /// </summary>
        public override event NotifyCollectionChangedEventHandler CollectionChanged;

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
            {
                using (IDisposable disposable = this.BlockReentrancy())
                {
                    foreach (Delegate del in this.CollectionChanged.GetInvocationList())
                    {
                        NotifyCollectionChangedEventHandler handler = (NotifyCollectionChangedEventHandler)del;
                        DispatcherObject dispatcherInvoker = del.Target as DispatcherObject;
                        ISynchronizeInvoke syncInvoker = del.Target as ISynchronizeInvoke;
                        if (dispatcherInvoker != null)
                        {
                            // We are running inside DispatcherSynchronizationContext,
                            // so we should invoke the event handler in the correct dispatcher.
                            dispatcherInvoker.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate
                            {
                                handler(this, e);
                            }));
                        }
                        else if (syncInvoker != null)
                        {
                            // We are running inside WindowsFormsSynchronizationContext,
                            // so we should invoke the event handler in the correct context.
                            syncInvoker.Invoke(del, new Object[] { this, e });
                        }
                        else
                        {
                            // We are running in free threaded context, so just directly invoke the event handler.
                            handler(this, e);
                        }
                    }
                }
            }
        }
    }
}

I've update the test sample to include this new version of BindableCollection, you can download it from here.

attachment:MultiThreadingInWPF.zip


 

1 comments:

Jmix90 said...

Very interesting, thanks !