1

I'm overriding the behavior of System.Windows.Data.CollectionView. There's a method that is supposed to clear and re-fill CollectionView.SourceCollection (which is an ObservableCollection<object> in my case) from database. Exception thrown:

Exception thrown: 'System.InvalidOperationException' in mscorlib.dll

Additional information: Collection was modified; enumeration operation may not execute.

It's thrown exactly the second time this line is hit SourceObservableCollection.Add(item);.

(commented lines describe my failed attempts to fix the problem):

    //...
    public ObservableCollection<object> SourceObservableCollection { get { return (ObservableCollection<object>)SourceCollection; } }

    //<Part of Attempt7>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
    {
        base.OnCollectionChanged(args);
        isCollectionChanging = false;
    }
    private bool isCollectionChanging = false;
    //</Part of Attempt7>
    //<Part of Attempt9>
    private static readonly object _lock = new object();
    //</Part of Attempt9>
    //<*async syntax is part of Attempt10*/>
    public async void RefreshSource()
    {
        SourceObservableCollection.Clear();

        // refreshSourceFunction retrieves data from Database
        IEnumerable result = refreshSourceFunction(/*parameters*/);

        ////Attempt1:
        foreach (object item in result)
        {
            SourceObservableCollection.Add(item);
        }

        ////Attempt2:
        //foreach (object item in result.OfType<object>().ToList())
        //{
        //    SourceObservableCollection.Add(item);
        //}

        ////Attempt3:
        //List<object> lstResult = result.OfType<object>().ToList();
        //foreach (object item in lstResult)
        //    SourceObservableCollection.Add(item);

        ////Attempt4:
        //List<object> lstResult2 = result.OfType<object>().ToList();
        //for (int x = 0; x < lstResult2.Count; x++)
        //{
        //    SourceObservableCollection.Add(lstResult2[x]);
        //}

        ////Attempt5:
        //IEnumerator enumerator = result.GetEnumerator();
        //while (enumerator.MoveNext())
        //{
        //    SourceObservableCollection.Add(enumerator.Current);
        //}

        ////Attempt6:
        //IEnumerator enumerator2 = result.GetEnumerator();
        //while (enumerator2.MoveNext())
        //{
        //    Dispatcher.Invoke(() =>
        //    {
        //        SourceObservableCollection.Add(enumerator2.Current);
        //    });
        //}

        ////Attempt7:
        //foreach (object item in result)
        //{
        //    isCollectionChanging = true;
        //    Dispatcher.Invoke(() =>
        //    {
        //        SourceObservableCollection.Add(item);
        //    }, System.Windows.Threading.DispatcherPriority.Background);
        //    while (isCollectionChanging) ;
        //}

        ////Attempt8:
        //foreach (object item in result)
        //{
        //    SourceObservableCollection.Add(item);
        //    Refresh();
        //}

        ////Attempt9:
        //foreach (object item in result)
        //{
        //    lock (_lock)
        //    {
        //        SourceObservableCollection.Add(item);
        //    }
        //}

        ////Attempt10:
        //await Dispatcher.InvokeAsync(() => SourceObservableCollection.Clear());
        //IEnumerable result2 = await Task.Run(() => refreshSourceFunction(/*parameters*/));
        //foreach (object item in result2)
        //{
        //    await Dispatcher.InvokeAsync(() => SourceObservableCollection.Add(item));
        //}
    }
    //...

Exception StackTrace had only this:

at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)

However, debug call stack was:

mscorlib.dll!System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource resource) Unknown

mscorlib.dll!System.Collections.Generic.List.Enumerator.MoveNextRare() Unknown

mscorlib.dll!System.Collections.Generic.List.Enumerator.MoveNext() Unknown

PresentationFramework.dll!MS.Internal.Data.IndexedEnumerable.EnsureEnumerator() Unknown

PresentationFramework.dll!MS.Internal.Data.IndexedEnumerable.EnsureCacheCurrent() Unknown

PresentationFramework.dll!MS.Internal.Data.IndexedEnumerable.Count.get() Unknown

PresentationFramework.dll!System.Windows.Data.CollectionView.Count.get() Unknown

PresentationFramework.dll!System.Windows.Data.CollectionView.AdjustCurrencyForAdd(int index) Unknown

PresentationFramework.dll!System.Windows.Data.CollectionView.ProcessCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs args) Unknown

PresentationFramework.dll!System.Windows.Data.CollectionView.OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs args) Unknown

System.dll!System.Collections.ObjectModel.ObservableCollection.OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) Unknown

System.dll!System.Collections.ObjectModel.ObservableCollection.InsertItem(int index, System.__Canon item) Unknown

mscorlib.dll!System.Collections.ObjectModel.Collection.Add(object item) Unknown

MyDll.dll!MyDll.MyNamespace.MyOverriddenCollectionView.RefreshSource() Line 105 C#

After observing debug stack trace, I became suspicious of MS.Internal.Data.IndexedEnumerable methods, especially after observing it in ReferenceSource; as you see, it's not safe for multi-threaded use:

    /// <summary>
    /// for a collection implementing IEnumerable this offers
    /// optimistic indexer, i.e. this[int index] { get; }
    /// and cached Count/IsEmpty properties and IndexOf method,
    /// assuming that after an initial request to read item[N],
    /// the following indices will be a sequence for index N+1, N+2 etc.
    /// </summary>
    /// <remarks>
    /// This class is NOT safe for multi-threaded use.
    /// if the source collection implements IList or ICollection, the corresponding
    /// properties/methods will be used instead of the cached versions
    /// </remarks>
    internal class IndexedEnumerable : IEnumerable, IWeakEventListener
    {
    //...

However, I still couldn't figure out how to get around that, or even what exactly goes wrong. Any help will be appreciated.

current .Net Framework version: 4.5

Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31
  • Can it be caused by that `result` is still being populated (in the method `refreshSourceFunction`), and you start to iterate it through (`foreach`)? – kennyzx Jul 25 '17 at 03:12
  • I thought of that, but if this was the case, it should've been solved by `.ToList()` in any of the attempts 2, 3, or 4. And even without `.ToList()`, `result` was never modified. @kennyzx – Aly Elhaddad Jul 25 '17 at 03:22
  • Dispatcher.BeginInvoke(() => // { // SourceObservableCollection.Add(item); // }, Try this as well lets see – – Ramankingdom Jul 25 '17 at 05:02

2 Answers2

1

It turned out that the problem was actually in ObservableCollection<T> itself as it's NOT thread-safe. It seems that it was being read in the UI thread while it was still being modified, and the threading-related work-around(s) described in the question didn't work because CollectionChanged event was being raised anyway. Replacing the type ObservableCollection<T> with a thread-safe version found here solved the problem.

Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31
0

This exception occurs when you are iterating over a collection and try to modify it. Generally when you iterate over an collection it returns an IEnumerable which you can assume as a pointer sequentially moving forward. let say you changed the collection then the iterator becomes invalid and framework throw invalid operation exception

For e.g (pseudo code)

Foreach(var item in collection)
{
    modify collection here; (Do some add , remove or clear operation)
}

In the above code the exception will be surely thrown

In order to handle this kind of scenario where you have to iterate over collection and wants to do some modify operation as well Use index

For e.g (Pseudo code)

for(int i=0; i< collection.count(); i++)
{
    // do anything here
}
Noam M
  • 3,156
  • 5
  • 26
  • 41
Ramankingdom
  • 1,478
  • 2
  • 12
  • 17