How to make ObservableCollection thread-safe?

38,752

Solution 1

You can create a simple thread friendly version of the observable collection. Like the following :

 public class MTObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
            if (CollectionChanged != null)
                foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
                {
                    DispatcherObject dispObj = nh.Target as DispatcherObject;
                    if (dispObj != null)
                    {
                        Dispatcher dispatcher = dispObj.Dispatcher;
                        if (dispatcher != null && !dispatcher.CheckAccess())
                        {
                            dispatcher.BeginInvoke(
                                (Action)(() => nh.Invoke(this,
                                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                                DispatcherPriority.DataBind);
                            continue;
                        }
                    }
                    nh.Invoke(this, e);
                }
        }
    }

with that now do a massive find & replace and change all your ObservableCollection to MTObservableCollection and your good to go

Solution 2

As of .net framwork 4.5 you can use native collection synchronization.

BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);

YourLockObject is instance of any object e.g. new Object();. Use one per collection.

This eliminates the need of some special class or anything. Just enable and enjoy ;)

[edit] As stated in the comments by Mark and Ed (thanks for clarifying!), this does not relieve you from locking the collection on updates as it just synchonizes the collection-view-binding and does not magically make the collection thread-safe itself. [/edit]

PS: BindingOperations resides in Namespace System.Windows.Data.

Solution 3

The solution Franck posted here will work in the case where one thread is adding things, but ObservableCollection itself (and List, which it's based on) are not thread-safe. If multiple threads are writing to the collection, hard-to-track-down bugs could be introduced. I wrote a version of ObservableCollection that uses a ReaderWriteLockSlim to be truly thread-safe.

Unfortunately, it hit the StackOverflow character limit, so here it is on PasteBin. This should work 100% with multiple readers/writers. Just like regular ObservableCollection, it's invalid to modify the collection in a callback from it (on the thread that received the callback).

Solution 4

You can use a ObservableConcurrentCollection class. They are in a package provided by Microsoft in the Parallel Extensions Extras library.

You can get it prebuilt by the community on Nuget: https://www.nuget.org/packages/ParallelExtensionsExtras/

Or get it from Microsoft here:

https://code.msdn.microsoft.com/ParExtSamples

Share:
38,752

Related videos on Youtube

ilight
Author by

ilight

Updated on April 13, 2020

Comments

  • ilight
    ilight about 4 years
    System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
    

    I am adding/removing from an ObservableCollection which is not on a UI thread.

    I have a method names EnqueueReport to add to the colleciton and a DequeueReport to remove from the colleciton.

    The flow of steps is as below :-

    1. 1.call EnqueueReport whenever a new report is requested
    2. call a method every few seconds to check if the report is generated (this has a foreach loop that checks the generated status of all reports in ObservableCollection)
    3. call DequeueReport if the report is generated

    I am not much in C# libraries. Can someone please guide me on this?

    • Jakub
      Jakub about 10 years
    • Aron
      Aron about 10 years
      With the .net framework you cannot bind to an Observable Collection and then edit it outside the UI thread. There are a few patterns around that, including defered UI update, bulk inserts or creating a threadsafe class implementation of CollectionView.
  • Robert Fraser
    Robert Fraser over 9 years
    This is NOT thread-safe because ObservableCollection<T> is not thread safe. It might work most of the time, but if multiple threads are writing to it, you're going to get reenterancy exceptions or corrupt data sooner or later, and that can be a really hard problem to track down.
  • Franck
    Franck over 9 years
    Typically you would not make an thread write/add directly into a non threaded object. You would use a progress event to update thread information as new items are being processed and ready to collect. Then the main thread can call asynchronously the insertion of item in that list. This list does asynchronous update through the dispatcher. But i don't know how you used it but i use it intensively and i constantly push and pull 2,250 to 2,525 items per seconds with 25 threads running on it. each push or pull between 90 and 101 items per seconds. It run 24/7 and still no issue.
  • Robert Fraser
    Robert Fraser about 9 years
    Ah OK I see... so basically you're only writing to it from one thread, just a different one from the UI.
  • Franck
    Franck about 9 years
    yes only 1 thread write but can be done asynchronously, so could be call twice at the same time.I prefer this methods as i need very high performance and direct access from thread is unnecessary when it can be avoided. But your solution do work well if you wan threads to access it directly. You will loose performance when you get to high amount of item because you recreate a new list in some places and when you add item to a list the capacity in memory double each time so now it will double a second time. But If you stay in the low hundreds of item i don't see much loss.
  • jnm2
    jnm2 about 9 years
    Wow, you went all out on this! It's perfect. Thanks.
  • Robert Fraser
    Robert Fraser about 9 years
    @jnm2 - I think the version I put up before didn't compile; uploaded a new one that doesn't reference any internal utility functions.
  • Robert Fraser
    Robert Fraser about 9 years
    The list is cleared before being recreated so it won't double in capacity again. If new items are added, it will double in capacity to accommodate them, of course. Also, Since it uses a WeakReference there shouldn't be any huge memory cost. If you have 30 threads enumerating it at once and 100,000 items it might be a problem, but as long as only the UI thread is accessing it, there will only be one snapshot list at any time.
  • jnm2
    jnm2 about 9 years
    Haha, I do that too. No worries.
  • Chad Grant
    Chad Grant over 7 years
    This is by no means thread safe, all you're doing is switching to the UI thread / Dispatcher. Not making the collection thread safe.
  • Philipp Munin
    Philipp Munin over 7 years
    Isn't it better to put it on gist.github.com?
  • avenmore
    avenmore about 7 years
    Not thread safe, but it does answer the OP's question. Perhaps the question should be re-titled "How to safely add to a UI Thread's ObservableCollection from a different thread". Anyway, this is what I was looking for when I searched for "thread safe observablecollection".
  • Tobias
    Tobias about 7 years
  • wilford
    wilford almost 7 years
    The collection works well, but it should really implement IDisposable since it has disposable fields like ReaderWriterLockSlim and ThreadLocal. In my scenario it ate up all available memory pretty quickly because it kept several large object graphs alive.
  • wonko realtime
    wonko realtime over 6 years
    There might be a race condition; after many hours of running i always get a "An ItemsControl is inconsistent with its items source" ... "Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset)."
  • Robert Fraser
    Robert Fraser over 6 years
    Uh oh; thanks for pointing that out. I'll look over it, but for now probably best to use the Microsoft official one that's received a lot more testing.
  • Anton Krouglov
    Anton Krouglov almost 6 years
    ObservableConcurrentCollection has somewhat limited functionality. Much better implementation is here codeproject.com/Articles/64936/…
  • shtse8
    shtse8 almost 6 years
    This should not be marked as an answer because this is not a thread-safe solution. It just dispatch the event in the correct thread, but not safe.
  • Franck
    Franck almost 6 years
    @shtse8 The question is not about thread safe collection. It is about accessing collection from another thread aka cross thread access. The dispatcher itself is thread safe but that make this kind of implementation thread friendly not thread safe.
  • Mark Gjøl
    Mark Gjøl over 5 years
    Note that this enables collection changes from other threads, but you still need to do synchronization if you are accessing the collection from multiple threads. Ie. the collection is not suddenly thread safe, but the binding in WPF is.
  • Martin
    Martin over 4 years
    @wonkorealtime If I understand the situation and the code correctly, there actually is the race condition you mentioned: WPF expects that "a change to the collection and the notification of that change (through INotifyCollectionChanged) are atomic; no access from other threads can intervene" (docs.microsoft.com/en-us/dotnet/api/…) Example: Worker thread does an Add, queues the event, UI thread reads Count, receives the event and sees an inconsistency.
  • ToolmakerSteve
    ToolmakerSteve about 4 years
    If I understand the code here correctly. BeginInvoke is asynchronous. But the methods we are trying to "fix" (e.g. Clear, Add, ..) are synchronous. So a "mass replace" of all ObservableCollections is a dangerous "fix" that will almost certainly introduce a new kind of bug, where a thread does an Add, and then runs code that "assumes" the item has indeed been added. BROKEN: it won't be added until the invoked code runs, some time later. For example: if collection is empty, add an item with default values. Now use collection[0] => exception (sometimes).
  • ToolmakerSteve
    ToolmakerSteve about 4 years
    To add to @MarkGjøl's comment "accessing ... from multiple threads": you need to do lock (YourLockObject){ .. } around any code that might be called from some thread other than the UI thread. That is, even if you are "only calling from one thread", be clear that the UI thread is already involved, so if you are (or might be) on some other thread, then locking is needed.
  • Franck
    Franck about 4 years
    @ToolmakerSteve This can't happen, at the moment the dispatcher is called the item is already added. This event trigger after the collection has changed. It just synchronize the databind values with the interface.
  • ToolmakerSteve
    ToolmakerSteve about 4 years
    Ah, thanks - I misunderstood the sequence of events behind the scenes.