集合被修改,枚举操作可能无法执行

Posted

技术标签:

【中文标题】集合被修改,枚举操作可能无法执行【英文标题】:Collection was modified, enumeration operation may not execute 【发布时间】:2012-04-13 01:58:51 【问题描述】:

我有多线程应用程序,但出现此错误

************** Exception Text **************
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   ...

我的收藏可能有问题,因为在一个线程上我阅读了我的收藏,而在另一个线程上我修改了收藏。

public readonly ObservableCollectionThreadSafe<GMapMarker> Markers = new ObservableCollectionThreadSafe<GMapMarker>();


public void problem()

  foreach (GMapMarker m in Markers)
  
    ...
  

我正在尝试使用此代码锁定集合,但不起作用。

public void problem()
    
       lock(Markers)
       
         foreach (GMapMarker m in Markers)
         
           ...
         
       
    

有解决这个问题的想法吗?

【问题讨论】:

你的问题是foreach里面的代码,请贴出来。 使用 foreach 循环时不能修改集合 【参考方案1】:

这是一个很常见的错误 - 在使用 foreach 迭代集合的同时修改集合,请记住 foreach 使用只读的 IEnumerator 实例。

尝试使用for() 循环遍历集合并进行额外的索引检查,因此如果索引超出范围,您将能够应用额外的逻辑来处理它。如果底层枚举没有实现ICollection,您还可以通过每次评估Count 值来使用LINQ 的Count() 作为另一个循环退出条件:

如果 Markers 实现 IColletion - 锁定 SyncRoot:

lock (Markers.SyncRoot)

使用for():

for (int index = 0; index < Markers.Count(); index++)

    if (Markers>= Markers.Count())
    
       // TODO: handle this case to avoid run time exception
    

您可能会发现这篇文章很有用:How do foreach loops work in C#?

【讨论】:

但是如果修改是通过从集合中删除一个项目,那将抛出一个IndexOutOfRange 异常 我提到了额外的索引检查以避免这个问题,将添加示例,感谢您指出这一点 我想用 for 替换 foreach 但后来我想如果锁定集合更好但不起作用:/ int i = mapMarkers.Markers.IndexOf(oldMarker); if (i != -1) mapMarkers.Markers[i] = newMarker; 由于ObservableCollectionThreadSafe 是您的自定义集合类,请显示添加/删除的代码,顺便说一句,您是否在添加/删除方法中锁定了this.SyncRoot【参考方案2】:

您需要同时锁定读取和写入端。否则,其中一个线程将不知道锁并尝试读取/修改集合,而另一个线程则在持有锁的情况下(分别)修改/读取

【讨论】:

【参考方案3】:

尝试读取您收藏的副本

foreach (GMapMarker m in Markers.Copy())

   ...

这将为您的集合创建一个新副本,该副本不会受到另一个线程的影响,但在大量集合的情况下可能会导致性能问题。

所以我认为如果你在读写过程中锁定集合会更好。

【讨论】:

...修改原始收藏。 你说得对,我认为使用.Copy 但这可能会导致性能问题。【参考方案4】:

您可以使用 foreach,但您必须将集合转换为列表并使用点运算符来访问行为方法。

示例:Markers.Tolist().ForEach(i => i.DeleteObject())

不完全确定你在用你的收藏做什么。我的示例假设您只想从集合中删除所有项目,但它可以应用于您尝试对集合执行的任何行为。

【讨论】:

【参考方案5】:

我通过使用解决了这个问题

var data = getData();
lock(data)

    return getData().Select(x => new DisplayValueModel(x));

而不是

return getData().Select(x => new DisplayValueModel(x));

【讨论】:

【参考方案6】:

这对我有用。对标记执行 ToList() 操作: foreach (GMapMarker m in Markers.ToList())

【讨论】:

以上是关于集合被修改,枚举操作可能无法执行的主要内容,如果未能解决你的问题,请参考以下文章

集合被修改枚举操作可能无法执行

C# 集合已修改;可能无法执行枚举操作

System.invalidoperationexception:集合被修改;枚举操作可能无法在 .net 5 api 项目中执行

修改了实体框架集合;枚举操作可能无法执行

C# - 集合已修改;枚举操作可能无法执行

集合已修改;可能无法执行枚举操作。