【问题标题】:System.InvalidOperationException: Collection was modifiedSystem.InvalidOperationException:集合已修改
【发布时间】:2010-11-12 08:46:33
【问题描述】:

我在通过队列枚举时遇到以下异常:

System.InvalidOperationException: 收藏已修改;枚举 操作可能无法执行

这里是代码摘录:

1:    private bool extractWriteActions(out List<WriteChannel> channelWrites)
2:    {
3:        channelWrites = new List<WriteChannel>();
4:        foreach (TpotAction action in tpotActionQueue)
5:        {
6:            if (action is WriteChannel)
7:            {
8:                channelWrites.Add((WriteChannel)action);
9:                lock(tpotActionQueue)
10:               {
11:                  action.Status = RecordStatus.Batched;
12:               }
13:           }
14:       }
15:       return (channelWrites.Count > 0);
16:   }

我想我理解这个问题 - 更改 action.Status = RecordStatus.Batched 的哈希表,这会搞砸枚举器上的 MoveNext()。 问题是,我如何正确实现该“模式”?

【问题讨论】:

  • 为什么要锁定队列?这段代码对我来说毫无意义。
  • @Kermit_xc:枚举器文档中更大的一点是“枚举器没有对集合的独占访问权;因此,通过集合枚举本质上不是线程安全的过程。为了保证线程枚举时的安全性,可以在整个枚举过程中锁定集合。要让集合被多线程访问读写,必须自己实现同步。”
  • 对。这段代码甚至没有做任何类似的事情。
  • 如果您需要在多个线程上同步读取器和写入器,请考虑使用读写器锁。

标签: c# collections thread-safety


【解决方案1】:

我认为您需要做的就是停止使用 foreach 并将其切换到 for 循环

for(int i = 0; i < tpotActionQueue.Length; i++)
{
     TpotAction action = tpotActionQueue[i];

     if (action is WriteChannel)
     {
        channelWrites.Add((WriteChannel)action);
        lock(tpotActionQueue)
        {
           action.Status = RecordStatus.Batched;
        }
     }
}

问候,迈克。

【讨论】:

  • 这在多线程环境下还是会出问题,看来tpotActionQueue是一个“全局”变量,在调用这个方法的时候可能会有另一个线程修改它。
  • 另外,您可以更改集合中的项目,所以我不明白这一点。
【解决方案2】:

我认为您在迭代 tpotActionQueue 时必须有其他线程修改它。由于您只是在 for 循环中锁定该队列,因此这是可能的。

【讨论】:

    【解决方案3】:

    您可以更改集合中项目的值。您收到的错误意味着添加或删除了一个项目,即:集合本身已被修改,而不是集合内的项目。这很可能是由另一个线程向此集合添加或删除项目引起的。

    您应该在方法开始时锁定您的队列,以防止其他线程在您访问它时修改集合。或者您甚至可以在调用此方法之前锁定集合。

    private bool extractWriteActions(out List<WriteChannel> channelWrites)
        {
          lock(tpotActionQueue)
          {
            channelWrites = new List<WriteChannel>();
            foreach (TpotAction action in tpotActionQueue)
            {
                if (action is WriteChannel)
                {
                    channelWrites.Add((WriteChannel)action);
    
                      action.Status = RecordStatus.Batched;
    
               }
            }
          }
           return (channelWrites.Count > 0);
       }
    

    【讨论】:

    • 该死! - 刚刚验证,在集合上有一个不同的线程推送元素,虽然弹出是线程安全的。不过,我有点担心整个迭代的“锁定”性能。它对时间非常敏感 - 它可能需要完全返工:{。不过谢谢!
    • @kermit_xs:“刚刚验证,有一个不同的线程在集合中推送元素”——这就是你的问题。从“foreach”更改为“for”循环不是您的问题,也不是答案。使用 foreach 时,您需要在枚举之前锁定您的集合。枚举器不是线程安全的。
    【解决方案4】:

    我想我在尝试从集合中删除项目的集合上使用foreach 循环时遇到了类似的异常(或者它可能是一个列表,我不记得了)。我最终通过使用for 循环解决了这个问题。或许可以尝试以下方法:

    for (int i=0; i<tpotActionQueue.Count(); i++)
    {
        TpotAction action = tpotActionQueue.Dequeue();
        if (action is WriteChannel)
        {
            channelWrites.Add((WriteChannel)action);
            lock(tpotActionQueue)
            {
                action.Status = RecordStatus.Batched;
            }
        }
    }
    

    【讨论】:

    • 是的,但此方法不会将项目删除或添加到集合中。问题不在此方法范围内。
    • 非常感谢,这确实有道理......会试一试。
    • No Stan 问题出在 action.Status 分配,请阅读任何集合的枚举文档。 “只要集合保持不变,枚举器就保持有效。如果对集合进行更改,例如添加、修改或删除元素,则枚举器将不可恢复地失效,并且其行为未定义”。在这里他正在修改一个元素
    • 您从未使用过foreach 来初始化列表?列表 t = 新列表(); t.Add(新测试()); t.Add(新测试()); foreach (t 中的测试项目) { item.SomeName = "SomeTest"; }
    • 这工作得很好......非常感谢这让我很头疼!
    【解决方案5】:

    您没有tpotActionQueue 的定义,但如果它只是普通的List&lt;TpotAction&gt;,那么该行不是您的问题。修改集合是添加或删除成员 - 不是在包含的对象上设置属性。

    你有一个lock(tpotActionQueue) 和一个线程安全标签,所以我猜在你枚举时还有另一个线程在tpotActionQueue 中添加或删除项目。您可能需要同步这些访问。

    【讨论】:

    • 这是我的想法 - 修改值不应该搞砸集合。最终我想把它从队列中删除,而是选择将它标记为“批处理”,这样它就可以在外面的主队列循环中得到照顾——虽然它是同一个线程。
    • 设置属性确实很重要,msdn.microsoft.com/en-us/library/4a9449ty.aspx
    【解决方案6】:

    来点 LINQy 怎么样?

    private bool extractWriteActions(out List<WriteChannel> channelWrites)
    {
    
       channelWrites= tpotActionQueue.Where<WriteChannel>(x => x is WriteChannel).ToList()
    
       foreach(WriteChannel channel in channelWrites) {
          channel.Status = RecordStatus.Batched;
       }
    
      return ( channelWrites.Count > 0);
    }
    

    【讨论】:

      猜你喜欢
      • 2020-02-07
      • 2022-06-17
      • 2011-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      相关资源
      最近更新 更多