Introduction

ObservableCollections are mainly used in WPF to notify bound UI components of any changes. Those notifications usually translate to UI component updates, normally due to binding. UI component changes could only occur in a UI thread. Whenever you have lengthy work to do, you should do those jobs on a worker thread to improve the responsiveness of the UI. But sometimes, UI updates are very lengthy too. In order to decouple the worker thread from the UI thread, I added/modified functions that enable me to process modifications on the ObservableCollection on the UI thread, either synchronously or asynchronously, and called from any thread. All that occurs transparently by calling the appropriate BeginInvoke (async) or Invoke (sync) whenever needed. But be careful: do not rely on any reads from the ObservableCollection from a worker thread if you use any asynchronous added functions.

Background

I found a few things but never exactly what I wanted. This is why I’m writing this article now. I took a look at: http://www.codeproject.com/KB/dotnet/MTObservableCollection.aspx (Paul Ortiz solution). And also http://powercollections.codeplex.com/. They weren’t what I expected. I also had some concerns about the first link (explained later).

Details

I decided to write my own multi-threaded ObservableCollection, but I got two unexpected major problems:

  • The first was that I started to program before thinking. I realised after many hours that a “lock” is per thread but an ObservableCollection does not belong to a thread. Every modification has to be done into the UI thread absolutely (due to the second problem).
  • The second is about what is expected from a UI component during an ObservableCollection notification. The expected state of an ObservableCollection from a UI component perspective is: on notification, the UI component expects that data is the same as the previous notification plus the content of the new notification itself, not more, not less. That implies a kind of single threaded modification only. That means that “lock” could not be used to add/remove/change scenarios for an asynchronous update *. But “locks” are still requested. They are needed to ensure that another thread will read valid data (while the UI thread updates the ObservableCollection).

* An asynchronous update means, as a difference from the Paul Ortiz solution, that the worker thread does not have to wait on the UI to update before continuing to process other things. My solution uses Dispatcher.BeginInvoke instead of Disptacher.Invoke.

I also had a few other problems in testing when I realised that CollectionView had one major constraint and a bug. CollectionView does not support range modification. CollectionView also has a bug in it because it does not use a “using” block around an iterator to ensure that Dispose is called (or use a foreach loop which is fine too). (See: https://connect.microsoft.com/VisualStudio/feedback/details/513500/collectionview-does-not-dispose-sourcecollection-enumerator-synchronously for those who have access.)

With all those restrictions, it is possible to use a worker thread and get better response. The trick is to do all of the lengthy work on a worker thread and send modification requests asynchronously to the UI thread, not just notifications. That implies using added functions with the suffix “Async”. The problem with my solution is that only the UI thread does “updates” (but it is a must). It means that you can rely on any reads to the collection while updates are in progress. You should use the "Async" functions for better performance only in cases where you do not have to read back the collection. Otherwise, you can use regular functions (synchronous) where they are overridden to ensure proper behavior when either called from the UI or the worker thread. If you don’t need async modification functions or other added functions, you can also use the lighter code from the Paul Ortiz solution (listed above). If you use Paul's solution, perhaps you would be better doing a little modification on the “Dispatcher” check (use Dispatcher.CheckAccess instead).

This is a list of many problems I had with the actual implementation of the ObservableCollection:

  1. Function Clear does not notify
  2. Unable to override critical functions
  3. No multi-threaded safe access
  4. Needs possibility to get blocking and unblocking iterators
  5. Possibility to get either “blocking” or “list copy” iterator

There is also an option that I was already looking for:

  1. Possibility to raise an event each time an item property changed (bubble up inner item PropertyChangedItem notifications if any).

Solution

I then decided to program my Swiss army knife collection. In fact, it is one collection for the multi-threaded, and another one (inheriting from the first) for adding item property changed notification.

The way I did it was a complete writing of the new class “ObservableCollectionMt” not inheriting from Collection, with a “List<T>” member containing every item. The class supports every interface the standard ObservableCollection supports.

Added methods/properties are:

  • GetEnumeratorCopy(), returns a copy of the inner list itself.
  • GetUnsafeEnum, returns an iterator of the inner list. To be used with care on a worker thread.
  • ClearNotifyOnce(), to notify once and only once. Be careful with CollectionView because it does not support range notification. You will get a “NotSupportedException”.
  • “Range actions are not supported”.
  • ClearNotifyForEach(), does what it means.
  • ClearNotifyOnce(), does what it means.
  • AddAsync(), to be used by the worker thread (could be faster).
  • InsertAsync(), to be used by the worker thread (could be faster).
  • RemoveAsync(), to be used by the worker thread (could be faster).
  • RemoveAtAsync(), to be used by the worker thread (could be faster).
  • ModifyAtAsync(), to be used by the worker thread (could be faster).
  • ClearAsync(), to be used by the worker thread (could be faster).
  • ClearNotifyOnceAsync(), to be used by the worker thread (could be faster).
  • DispatchPriorityCall, used to determine what you prioritizes (speed over UI responsiveness); the default is DispatcherPriority.Background for UI responsiveness.

The class that inherits from ObservableCollectionMt and supports item property notifications is ObservableCollectionMtNotifyItemChanged.

I have included a sample with threading access to show the main usage.

Hope you will like it.

History

  • Apr 28, 2011: Updated source code. Updated the Introduction section.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"