Update 2013-01-18 / Important note before usage...
My implementation has some bugs that could happen in rare circumstances. But it could happen. In fact a real MT ObservableCollection is impossible to my opinion due to this two interdependent points:
- To benefits of MT, you should not wait for any event handlers
- Not waiting for event handlers would compromise the expected behavior --> for example, a ui control implementation (grid) that would receive an event "
ItemAdd" would expect the collection to be the same as it was before the event with and only the newly added item, which is not the case in MT.
Because of this. If hi performance is the target and you could live with two constraints : UI view of the obs coll could be readonly and duplicate of item is acceptable... I would highly recommend the usage of: "
CollectionMtWithAsyncObservableCollectionReadOnlyCopy" which I made it available here. Some explanation of the usage is available at the beginning of the class definition. I personnally use it at many places with no problem. I changed every usage of
CollectionMtWithAsyncObservableCollectionReadOnlyCopy. There is impacts but they are low. But don't forget to bind to the ObsColl property instead of binding directly to the instance.
CollectionMtWithAsyncObservableCollectionReadOnlyCopy works is modifying an internal list but synchronously add task to the queue of the dispatcher to duplicate the same action (add/remove) on the internal ObsColl. ObsColl will be updated whenever the dispatcher will reach the task. Both collection should be in sync when the dispatcher queue has processed every tasks (verified).
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.
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).
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
* 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
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
This is a list of many problems I had with the actual implementation of the ObservableCollection:
Clear does not notify
- Unable to override critical functions
- No multi-threaded safe access
- Needs possibility to get blocking and unblocking iterators
- Possibility to get either “blocking” or “list copy” iterator
There is also an option that I was already looking for:
- Possibility to raise an event each time an item property changed (bubble up inner item
PropertyChangedItem notifications if any).
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 “
- “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
I have included a sample with threading access to show the main usage.
Hope you will like it.
- Jan 18, 2013: Added information about "
- Apr 28, 2011: Updated source code. Updated the Introduction section.
- Jun 23, 2011: Updated source code.