26

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 collection and a DequeueReport to remove from the collection.

The flow of steps is as below:

  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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
ilight
  • 1,622
  • 2
  • 22
  • 44
  • 1
    Try this one [ObservableCollection and threading](http://stackoverflow.com/questions/2293246/observablecollection-and-threading) – Jakub Apr 16 '14 at 11:28
  • 1
    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. – Aron Apr 16 '14 at 11:29

6 Answers6

36

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.

FastJack
  • 866
  • 1
  • 10
  • 11
  • 4
    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. – Mark Gjøl Mar 28 '19 at 13:52
  • 1
    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. – ToolmakerSteve Apr 18 '20 at 01:10
21

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

Robert Fraser
  • 10,649
  • 8
  • 69
  • 93
  • Wow, you went all out on this! It's perfect. Thanks. – jnm2 Apr 16 '15 at 17:08
  • @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 Apr 17 '15 at 06:38
  • Haha, I do that too. No worries. – jnm2 Apr 17 '15 at 12:48
  • Isn't it better to put it on gist.github.com? – Philipp Munin Mar 22 '17 at 22:36
  • 1
    @Philipp Munin I put it on gist https://gist.github.com/anonymous/26d9d070619de58fa8e28ea21fff04fd – Tobias May 31 '17 at 06:49
  • 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. – wilford Aug 11 '17 at 12:02
  • 1
    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)." – wonko realtime Jan 27 '18 at 09:40
  • 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. – Robert Fraser Jan 31 '18 at 19:59
  • 2
    @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" (https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netframework-4.8) Example: Worker thread does an Add, queues the event, UI thread reads Count, receives the event and sees an inconsistency. – Martin Jan 30 '20 at 09:54
14

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

Franck
  • 4,438
  • 1
  • 28
  • 55
  • 26
    This is NOT thread-safe because ObservableCollection 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. – Robert Fraser Mar 26 '15 at 20:19
  • 4
    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. – Franck Mar 27 '15 at 17:20
  • Ah OK I see... so basically you're only writing to it from one thread, just a different one from the UI. – Robert Fraser Apr 08 '15 at 22:41
  • 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. – Franck Apr 09 '15 at 11:19
  • 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. – Robert Fraser Apr 17 '15 at 06:53
  • 3
    This is by no means thread safe, all you're doing is switching to the UI thread / Dispatcher. Not making the collection thread safe. – Chad Grant Dec 09 '16 at 18:34
  • 4
    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". – avenmore Apr 03 '17 at 08:46
  • 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. – shtse8 Aug 16 '18 at 18:44
  • 1
    @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. – Franck Aug 16 '18 at 18:52
  • 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 Apr 17 '20 at 23:47
  • @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. – Franck Apr 20 '20 at 12:12
  • Ah, thanks - I misunderstood the sequence of events behind the scenes. – ToolmakerSteve Apr 21 '20 at 18:18
12

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

Greg
  • 1,549
  • 1
  • 20
  • 34
  • ObservableConcurrentCollection has somewhat limited functionality. Much better implementation is here https://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anton Krouglov Jul 08 '18 at 21:57
1

This article is written for Xamarin.Forms users, but it may apply to anyone needing to make ObservableCollections thread-safe:

https://codetraveler.io/2019/09/11/using-observablecollection-in-a-multi-threaded-xamarin-forms-application/

It's a very short solution.

After the collection has been initialized, add this:

Xamarin.Forms.BindingBase.EnableCollectionSynchronization(MyCollection, null, ObservableCollectionCallback);

And add this method to the same class:

    void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
    {
        // `lock` ensures that only one thread access the collection at a time
        lock (collection)
        {
            accessMethod?.Invoke();
        }
    }

The author is Brandon Minnick.

Le Mot Juiced
  • 3,761
  • 1
  • 26
  • 46
1
public class ObservableCollectionThreadSafe<T>
    : ObservableCollection<T>, IDisposable
{
    #region Data
    private Dispatcher _dispatcher;
    private ReaderWriterLockSlim _lock;
    #endregion

    #region Constructor
    public ObservableCollectionThreadSafe()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
        _lock = new ReaderWriterLockSlim();
    }
    #endregion


    #region Overrides

    /// <summary>
    /// Clear all items
    /// </summary>
    protected override void ClearItems()
    {
        _dispatcher.InvokeIfRequired(() =>
            {
                _lock.EnterWriteLock();
                try
                {
                    base.ClearItems();
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }, DispatcherPriority.DataBind);
    }

    /// <summary>
    /// Inserts an item
    /// </summary>
    protected override void InsertItem(int index, T item)
    {
        _dispatcher.InvokeIfRequired(() =>
        {
            if (index > this.Count)
                return;

            _lock.EnterWriteLock();
            try
            {
                base.InsertItem(index, item);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);

    }

    /// <summary>
    /// Moves an item
    /// </summary>
    protected override void MoveItem(int oldIndex, int newIndex)
    {
        _dispatcher.InvokeIfRequired(() =>
        {
            _lock.EnterReadLock();
            int itemCount = this.Count;
            _lock.ExitReadLock();

            if (oldIndex >= itemCount |
                newIndex >= itemCount |
                oldIndex == newIndex)
                return;

            _lock.EnterWriteLock();
            try
            {
                base.MoveItem(oldIndex, newIndex);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);



    }

    /// <summary>
    /// Removes an item
    /// </summary>
    protected override void RemoveItem(int index)
    {

        _dispatcher.InvokeIfRequired(() =>
        {
            if (index >= this.Count)
                return;

            _lock.EnterWriteLock();
            try
            {
                base.RemoveItem(index);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);
    }

    /// <summary>
    /// Sets an item
    /// </summary>
    protected override void SetItem(int index, T item)
    {
        _dispatcher.InvokeIfRequired(() =>
        {
            _lock.EnterWriteLock();
            try
            {
                base.SetItem(index, item);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);
    }
    #endregion

    #region Public Methods
    /// <summary>
    /// Return as a cloned copy of this Collection
    /// </summary>
    public T[] ToSyncArray()
    {
        _lock.EnterReadLock();
        try
        {
            T[] _sync = new T[this.Count];
            this.CopyTo(_sync, 0);
            return _sync;
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                if (_lock != null)
                    _lock.Dispose();
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.

            disposedValue = true;
        }
    }

    // TODO: override a finalize only if Dispose(bool disposing) above has code to free unmanaged resources.
    // ~ObservableCollectionThreadSafe() {
    //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    //   Dispose(false);
    // }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
        // TODO: uncomment the following line if the finalize is overridden above.
        // GC.SuppressFinalize(this);
    }
    #endregion

    #endregion
}

/// <summary>
/// WPF Threading extension methods
/// </summary>
public static class WPFControlThreadingExtensions
{
    #region Public Methods
    /// <summary>
    /// A simple WPF threading extension method, to invoke a delegate
    /// on the correct thread if it is not currently on the correct thread
    /// Which can be used with DispatcherObject types
    /// </summary>
    /// <param name="disp">The Dispatcher object on which to do the Invoke</param>
    /// <param name="performAction">The delegate to run</param>
    /// <param name="priority">The DispatcherPriority</param>
    public static void InvokeIfRequired(this Dispatcher disp,
        Action performAction, DispatcherPriority priority)
    {
        if (disp.Thread != Thread.CurrentThread)
        {
            disp.Invoke(priority, performAction);
        }
        else
            performAction();
    }
    #endregion
}
Sandeep Jadhav
  • 815
  • 1
  • 10
  • 27