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

14

我有一个多线程应用程序,我遇到了这个错误

************** 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)
         {
           ...
         }
       }
    }

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


2
你的问题出在foreach内部的代码,请把它贴出来。 - nemesv
3
在使用foreach循环遍历集合时,不能修改该集合。 - Renatas M.
ObservableCollectionThreadSafe<T>是什么?如果它是一个自定义集合,你能否在问题中包含它? - Theodor Zoulias
5个回答

20
这是一个相当常见的错误-在使用foreach迭代集合时修改它,请注意foreach使用只读的IEnumerator实例。
尝试使用带有额外索引检查的for()循环遍历集合,因此如果索引超出边界,您将能够应用其他逻辑来处理它。如果底层枚举没有实现ICollection,还可以使用LINQ的Count()作为另一个循环退出条件,通过每次计算Count值来评估它:
如果Markers实现了ICollection - 在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
    }
}

你可能会发现这篇文章很有用:如何在C#中使用foreach循环?


但如果修改是通过从集合中删除一个项目来完成的,那么将会抛出一个“IndexOutOfRange”异常。 - Amir Ismail
1
我提到了额外的索引检查来避免这个问题,将添加示例,感谢您指出这一点。 - sll
int i = mapMarkers.Markers.IndexOf(oldMarker); if (i != -1) { mapMarkers.Markers[i] = newMarker; } - PATO7
由于ObservableCollectionThreadSafe是您的自定义集合类,请展示Add/Remove的代码。顺便问一下,您在Add/Remove方法中是否锁定了this.SyncRoot - sll
是的,“回归”到普通的for循环对我也起了作用。 - AndyUK
显示剩余4条评论

4

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


4
尝试读取您收藏的克隆。
foreach (GMapMarker m in Markers.Copy())
{
   ...
}

这将创建一个新的集合副本,不会受到其他线程的影响,但可能会在大型集合的情况下导致性能问题。
因此,我认为最好在读写过程中锁定集合。

...并修改原始集合。 - Renatas M.
你是对的,我认为使用.Copy可能会导致性能问题。 - Amir Ismail

-1

这对我有用。在Markers上执行ToList()操作:

foreach (GMapMarker m in Markers.ToList())

-2

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接