Asynchronous Multi-threaded ObservableCollection
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:
- Function
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).
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 withCollectionView
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 isDispatcherPriority.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.
发表评论
pIaaw4 Nice info! Also visit my blog about Clomid challenge test
UX3VeF Thank you for sharing your thoughts. I really appreciate your efforts and I am waiting for your next write ups thank you once again.
gprADO This awesome blog is definitely awesome additionally factual. I have found helluva useful tips out of this amazing blog. I ad love to go back over and over again. Cheers!
KOJMIV Wow, great blog article.Really looking forward to read more. Keep writing.
KCkHNQ It is hard to locate knowledgeable individuals with this topic, however you seem like there as more that you are referring to! Thanks
fP2ens Thanks for the article.Really looking forward to read more. Really Cool.
VO3n9I This website definitely has all of the info I needed about this subject and didn at know who to ask.
D4vUrB Hi my family member! I want to say that this article is awesome, great written and come with approximately all significant infos. I'd like to see extra posts like this .
Yz2zjE Very good written information. It will be beneficial to anyone who utilizes it, as well as myself. Keep doing what you are doing - i will definitely read more posts.
F8q4g5 Heya i am for the primary time here. I found this board and I in finding It really helpful & it helped me out much. I am hoping to offer one thing again and aid others such as you aided me.
BjfJCB Hello! I simply would like to give a huge thumbs up for the nice information you might have right here on this post. I will be coming again to your blog for extra soon.
fR9dsS I appreciate you sharing this post.Really looking forward to read more. Keep writing.
5dgYA9 Major thanks for the post.Really thank you! Great.
W1e4ql I value the blog article.Really thank you! Want more.
YzgdWI Im grateful for the article post.Really looking forward to read more. Much obliged.
SCJFa2 I appreciate you sharing this post. Great.
8lI0Wp Major thanks for the blog article.Really looking forward to read more. Much obliged.
7DDZgg I appreciate you sharing this article. Keep writing.
fE6tjf Thank you ever so for you post.Much thanks again. Cool.
3QZdQV Very informative blog.Really looking forward to read more. Awesome.
X5iySS I loved your blog article.Thanks Again. Great.
UlrWNz Thanks a lot for the article.Thanks Again. Want more.
kWGI1C Hey, thanks for the post.Much thanks again. Will read on...