【问题标题】:WPF CollectionView Error: 'Collection was modified; enumeration operation may not execute.'WPF CollectionView 错误:'集合已修改;枚举操作可能无法执行。
【发布时间】:2017-07-25 02:27:21
【问题描述】:

我正在覆盖System.Windows.Data.CollectionView 的行为。有一种方法应该从数据库中清除并重新填充CollectionView.SourceCollection(在我的情况下是ObservableCollection<object>)。抛出异常:

抛出异常:mscorlib.dll 中的“System.InvalidOperationException”

附加信息:集合已修改;枚举操作 可能无法执行。

这条线第二次被击中SourceObservableCollection.Add(item);时就抛出了。

注释行描述了我解决问题的失败尝试):

    //...
    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 只有这个:

在 System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource 资源)

但是,调试调用堆栈是:

mscorlib.dll!System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource 资源)未知

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

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

PresentationFramework.dll!MS.Internal.Data.IndexedEnumerable.EnsureEnumerator() 未知

PresentationFramework.dll!MS.Internal.Data.IndexedEnumerable.EnsureCacheCurrent() 未知

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

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

PresentationFramework.dll!System.Windows.Data.CollectionView.AdjustCurrencyForAdd(int 索引)未知

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

PresentationFramework.dll!System.Windows.Data.CollectionView.OnCollectionChanged(object 发件人, System.Collections.Specialized.NotifyCollectionChangedEventArgs args) 未知

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

System.dll!System.Collections.ObjectModel.ObservableCollection.InsertItem(int index, System.__Canon 项目)未知

mscorlib.dll!System.Collections.ObjectModel.Collection.Add(object 项目)未知

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

观察调试堆栈跟踪后,我开始怀疑MS.Internal.Data.IndexedEnumerable 方法,尤其是在observing it in ReferenceSource 之后;如您所见,多线程使用并不安全:

    /// <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
    {
    //...

但是,我仍然不知道如何解决这个问题,甚至不知道到底出了什么问题。任何帮助将不胜感激。

当前 .Net Framework 版本:4.5

【问题讨论】:

  • 会不会是因为result 仍在被填充(在refreshSourceFunction 方法中),而你开始迭代它(foreach)?
  • 我是这么想的,但如果是这样的话,.ToList() 在任何尝试 2、3 或 4 中都应该解决它。即使没有.ToList(),@ 987654335@ 从未被修改过。 @kennyzx
  • Dispatcher.BeginInvoke(() => // { // SourceObservableCollection.Add(item); // },也试试这个让我们看看 –

标签: c# .net wpf observablecollection collectionview


【解决方案1】:

原来问题实际上出在ObservableCollection&lt;T&gt; 本身,因为它不是线程安全的。似乎它仍在修改时在 UI 线程中被读取,并且问题中描述的与线程相关的解决方法不起作用,因为无论如何都会引发 CollectionChanged 事件。将类型 ObservableCollection&lt;T&gt; 替换为线程安全版本 found here 解决了问题。

【讨论】:

    【解决方案2】:

    当您迭代集合并尝试修改它时,会发生此异常。通常,当您遍历一个集合时,它会返回一个 IEnumerable,您可以将其假定为按顺序向前移动的指针。假设您更改了集合,然后迭代器变得无效并且框架抛出无效操作异常

    例如(伪代码)

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

    上面的代码肯定会抛出异常

    为了处理这种你必须迭代集合并且还想做一些修改操作的场景,请使用索引

    例如(伪代码)

    for(int i=0; i< collection.count(); i++)
    {
        // do anything here
    }
    

    【讨论】:

    • 如您所见,我正在迭代的IEnumerable (result) 从未被修改过。
    • 结果来自 SourceCollection 是吗?如果是这种情况,您也可以在 Loop 中使用 result.ToList()。
    • 恐怕不是;它来自数据库。
    • 嗯。我明白你的意思了。在这种情况下,我们可以做的是委托声明。指向它的方法。
    • Dispatcher.BeginInvoke(() => // { // SourceObservableCollection.Add(item); // }, 试试这个让我们看看
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-05
    • 2015-06-17
    • 2020-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    相关资源
    最近更新 更多