Welcome to Part 2 of our series Making a Better ObservableCollection. If you missed Making a Better ObservableCollection Part 1 – Extensions you can get to it here.
In this next section I am going to share a version of my ObservableCollectionEx that allows cross-threading. The idea here is to have an ObservableCollection which you can update from an Async thread so as not to impact the owning thread. This is especially useful in WPF when you don’t wish to block the UI thread while performing collection updates.
Let’s see the code:
/// <summary> /// Initializes a new instance of the /// <see cref="ObservableCollectionEx{T}"/> class. /// </summary> public class ObservableCollectionEx<T> : ObservableCollection<T> { #region Constructors /// <summary> /// Initializes a new instance of the /// <see cref="ObservableCollectionEx{T}" /> class. /// </summary> public ObservableCollectionEx() { } /// /// Initializes a new instance of the /// class. /// ///The collection. public ObservableCollectionEx(IEnumerable<T> collection) : this() { this.AddRange(collection); } #endregion #region Events /// <summary> /// Source: New Things I Learned /// Title: Have worker thread update ObservableCollection that is bound to a ListCollectionView /// http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx /// Note: Improved for clarity and the following of proper coding standards. /// </summary> /// <param name="e"></param> protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { // Use BlockReentrancy using (BlockReentrancy()) { var eventHandler = CollectionChanged; if (eventHandler == null) return; // Only proceed if handler exists. Delegate[] delegates = eventHandler.GetInvocationList(); // Walk through invocation list. foreach (var @delegate in delegates) { var handler = (NotifyCollectionChangedEventHandler)@delegate; var currentDispatcher = handler.Target as DispatcherObject; // If the subscriber is a DispatcherObject and different thread. if ((currentDispatcher != null) && (!currentDispatcher.CheckAccess())) { // Invoke handler in the target dispatcher's thread. currentDispatcher.Dispatcher.Invoke( DispatcherPriority.DataBind, handler, this, e); } else { // Execute as-is handler(this, e); } } } } /// <summary> /// Overridden NotifyCollectionChangedEventHandler event. /// </summary> public override event NotifyCollectionChangedEventHandler CollectionChanged; #endregion }
The constructors are pretty straight forward. We want to have an empty constructor and one that allows an immediate “AddRange” of an IEnumerable just like List and ObservableCollection allow, but that is not really the point of this post.
The main feature here is a slightly reformatted version of a wonderful post from a blog called New Things I Learned which covers cross-thread access with an ObservableCollection.
So, what does this code do?
- First we use BlockReentracy to prevent changes to the collection while we are evaluating it.
- Next, we get the Invocation List from the CollectionChanged event handler. This is the list of delegates subscribing to the event.
- Evaluate each delegate by retrieving it’s Target and casting it to a DispatcherObject.
- Make sure the DispatcherObject is valid and that the current thread has access.
- Invoke the delegate in the DispatcherObject’s thread.
It’s not the simplest code to follow when you aren’t overly familiar with working with event delegates but it gets the job done quite well.
Next time, we will talk about using a custom SortComparer to improve DataGrid performance.
Until next time.
Good job for WPS but how would one do the same for Silverlight 5. It doesn’t have the BlockReentrancy and CheckReentrancy methods at all. I would really appreciate if you could provide some feedback.
Regards
Nas