Click here to Skip to main content
15,885,141 members
Articles / Programming Languages / C#

Asynchronous Multi-threaded ObservableCollection

Rate me:
Please Sign up or sign in to vote.
4.85/5 (18 votes)
9 May 2014CPOL6 min read 124.4K   4.4K   112   40
An implementation of of a multi-threaded ObservableCollection.

Update 2014-04-18

  • Updated with .net Framework 4.5 (Now required)
  • Support some functionnalities to manage the collection as a recorder of changes (added/removed items): IsRecording, GetCopyOfRecordedItemsNew, GetCopyOfRecordedItemsRemoved
  • Support the new .net 4.5 interface: IReadOnlyList
  • Support usage of the collection as a stack with: Push and TryPop
  • Support usage of the collection as a queue with: Enqueue and TryDequeue (not optimized for queue but works)
  • Fixed some minors bugs

Update 2013-01-18 / Important note before usage...

My original implementation had some bugs that could happen in rare circumstances. But it could happen. In fact a real MT ObservableCollection (ObsCollMt) 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 maintain a list and an observable collection at the ~ same time)... 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 ObservableCollectionMt to 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.

The way 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).

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 created a collection which has in its internal 2 collections:

  • A list which could be accessed in a multithreaded context. Major properties/functions of a regular collection are supported. Major properties/functions are the same as a regular collection. Some functions that could potentially be thread unsafe are clearly indicated (start with 'Unsafe').
  • An ObservableCollection which should be used as read only from the UI. This collection is accessible through the ObsColl property. This ObsColl should be threaded as readonly otherwise it could lead to inconsitent data between both the list and the ObsColl.

Every modifications done to the ObsCollMt are done to the first internal list but are also queued to the dispatcher in the exact same order as they happen in the MT context (lock ensures this). The ObsColl should be an exact copy of the first internal list after UI would have processed all of its messages. But accessing the ObsCollMt would not suffer of waiting UI updates because of usage of Dispatcher.BeginInvoke.

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 an unexpected major problem:

  • The major one was discovered after few attempts of making an ObservableCollection MT without dual copy of the data. I think, as explained at the beginning, to decouple UI from MT worker thread and keep data coherent, we should have dual list of the data, one for workers and one for UI. Also, the copy used for the UI should not be modify of if so, then is should be not used by any workers.

* 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.)

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

Solution

I then decided to program my Swiss army knife collection. In fact, it is one collection for the multi-threaded, and a regular ObservableCollection. When accessing the ObsCollMt, you modify a regular list (with MT safe lock) and add notification to update the regular ObsColl in the UI thread.

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

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

Do not forget to bind to "ObsColl" property instead of bind directly to the collection itself.

Hope you will like it.

History

  • April, 28, 2014, Made some corrections in text.
  • June, 03, 2013, Made a requested correction about verification of "Application.Current" not being null. Removed old source code which is a little bugged to remove confusion and to ensure to only use the new version with copy.
  • Mai, 31, 2013, Put code in a little lib, added a little test project, make project for VS2010 instead of VS 2012 (easier for more people to try)
  • Jan 18, 2013: Added information about "CollectionMtWithAsyncObservableCollectionReadOnlyCopy"
  • Apr 28, 2011: Updated source code. Updated the Introduction section.
  • Jun 23, 2011: Updated source code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer IREQ - Hydro-Quebec
Canada Canada
Hi,

I'm a french Canadian software developer.
Work at Hydro-Quebec / IREQ.
Live in Sainte-Julie, Quebec, Canada (South shore of Montreal)

WebPage: http://www.ericouellet.com
GMail: ericouellet2

Salut!

Comments and Discussions

 
GeneralRe: A bug in ObservableCollectionMt<T> class Pin
Eric Ouellet11-Feb-13 3:27
professionalEric Ouellet11-Feb-13 3:27 
GeneralRe: A bug in ObservableCollectionMt<T> class Pin
jogibear99881-Jun-13 13:51
jogibear99881-Jun-13 13:51 
GeneralRe: A bug in ObservableCollectionMt<T> class Pin
Eric Ouellet3-Jun-13 3:47
professionalEric Ouellet3-Jun-13 3:47 
GeneralRe: A bug in ObservableCollectionMt<T> class Pin
Eric Ouellet28-Apr-14 5:12
professionalEric Ouellet28-Apr-14 5:12 
GeneralMy vote of 5 Pin
Gunpal522-Dec-11 8:15
Gunpal522-Dec-11 8:15 
GeneralNice article! Pin
Wonde Tadesse10-Nov-11 15:26
professionalWonde Tadesse10-Nov-11 15:26 
GeneralRe: Nice article! Pin
Eric Ouellet14-Nov-11 4:02
professionalEric Ouellet14-Nov-11 4:02 
QuestionMy vote of 5 Pin
Filip D'haene23-Jun-11 9:58
Filip D'haene23-Jun-11 9:58 
AnswerRe: My vote of 5 Pin
Eric Ouellet23-Jun-11 10:06
professionalEric Ouellet23-Jun-11 10:06 
GeneralNice Pin
Member 456543329-Apr-11 3:33
Member 456543329-Apr-11 3:33 
GeneralRe: Nice Pin
Eric Ouellet29-Apr-11 4:42
professionalEric Ouellet29-Apr-11 4:42 
GeneralRe: Nice Pin
Member 456543329-Apr-11 4:57
Member 456543329-Apr-11 4:57 
GeneralRe: Nice Pin
Eric Ouellet29-Apr-11 5:02
professionalEric Ouellet29-Apr-11 5:02 
GeneralRe: Nice [modified] Pin
Eric Ouellet29-Apr-11 6:38
professionalEric Ouellet29-Apr-11 6:38 
Wow and mmmm

I feel strange... like a mixed bag of feelings...
I would like to say many things...

1 - I have to say something that probably does not help to see it very positively. I'm totally frustrated and very extremely shock that Microsoft does not enables me to update the UI from more than more thread. I had that limitation in COM with appartment and it still exists 15 years after. C# seems to evolve quickly but majors concerns are still alive !!! (my opinion).

2 - I wonder if some C# features become alive due to the limitation of single UI thread updating. I'm not sure but I think that perhaps few things that become alive now and in the near future will potentially die while we will be able (if it happen) to update the UI from any threads. In fact every things that depends on the ThreadContext. I really question myself on the priority that Microsoft have ? Fix UI thread update of find any twisted way to workaround it ?

3 - C# Async ... It seems nice but not so simple at first sight. The video is very good but personnaly i'm missing information about ThreadContext and who will run the callback code. It looks like that the UI will run it if it was the UI that initiate it... But how it could know it has to run on the UI thread. What happen if it is a worker thread who start it? I'm a bit mixed up. In fact... I'm totally mixed up and I don't understand at all how it could work (how the magic could happen). But I should say that my knowledge is limited and I have to understand 99% before I say that I understand (it is not the fact right now).

4 Due to point 3 (my not understanding), I can't realize where to use that in my MT ObservableCollection. I can't see some nice applications mainly while waiting on web. I can think that I could use it to call my lenghty job but still .... OUPS THE LIGHT LIGHT UP NOW ! I can create a task for each item that would feed my ObservableCollection (one task per Item to add in my collection). But still, I can't see how to use it for my MT OC. But instead, I can modify my code that call the OC to take advantage of the "C# Async" new features and use a regular OC. But doing so will put greater (not that much but) complexity in my code that otherwise I wouldn't have by using a MT OC that hide that complexity for me.

Hey... my answer is not simple, kind of like the new "C# Async" feature Smile | :) . I'm agree they did a very good job to make it simple to use but I also think that we have to invest few (many) hours to undertsand exactly what happen behinds to use it properly.

Thanks again for bringing me that to my attention. I have no time to test that now (that is only beta and I'm very busy) but I will definitively use it in the future while any need will requested it... and I'm very happy to know it (will) exists.

Thanks a lot
Eric
modified on Tuesday, May 3, 2011 11:24 AM

QuestionLock is per "thread"? Pin
Jeffrey Schultz21-Apr-11 9:31
Jeffrey Schultz21-Apr-11 9:31 
AnswerRe: Lock is per "thread"? Pin
Eric Ouellet21-Apr-11 11:33
professionalEric Ouellet21-Apr-11 11:33 
GeneralI do it this way (may be of interest) Pin
Sacha Barber18-Apr-11 9:03
Sacha Barber18-Apr-11 9:03 
GeneralRe: I do it this way (may be of interest) Pin
Eric Ouellet18-Apr-11 9:31
professionalEric Ouellet18-Apr-11 9:31 
GeneralRe: I do it this way (may be of interest) Pin
Sacha Barber18-Apr-11 9:37
Sacha Barber18-Apr-11 9:37 
GeneralRe: I do it this way (may be of interest) Pin
Eric Ouellet21-Apr-11 11:41
professionalEric Ouellet21-Apr-11 11:41 
GeneralRe: I do it this way (may be of interest) Pin
Sacha Barber21-Apr-11 20:35
Sacha Barber21-Apr-11 20:35 
GeneralRe: I do it this way (may be of interest) Pin
KenBeckett30-Oct-11 16:44
KenBeckett30-Oct-11 16:44 
GeneralRe: I do it this way (may be of interest) Pin
Sacha Barber30-Oct-11 21:34
Sacha Barber30-Oct-11 21:34 
GeneralRe: I do it this way (may be of interest) Pin
Eric Ouellet31-Oct-11 2:53
professionalEric Ouellet31-Oct-11 2:53 
GeneralRe: I do it this way (may be of interest) Pin
KenBeckett31-Oct-11 11:47
KenBeckett31-Oct-11 11:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.