【问题标题】:Threadsafe foreach enumeration of lists线程安全的 foreach 枚举列表
【发布时间】:2008-09-15 20:29:54
【问题描述】:

我需要枚举对象的通用 IList。列表的内容可能会发生变化,例如被其他线程添加或删除,这将终止我的枚举,并显示“集合已修改;枚举操作可能无法执行。”

在 IList 上执行线程安全 foreach 的好方法是什么?最好不要克隆整个列表。无法克隆列表引用的实际对象。

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    克隆列表是最简单和最好的方法,因为它可以确保您的列表不会因您而改变。如果列表太大而无法克隆,请考虑在其周围加一个锁,在读取/写入之前必须对其进行锁定。

    【讨论】:

    • “锁定感知”枚举器怎么样?读取锁一直保持到最后一个元素被读取(movenext 返回 false?)
    • @John 为了克隆列表,您必须对其进行迭代。以非线程安全的方式。那么这和从列表中获取不一样吗?
    • @JonSkeet 你能看一下吗
    • 克隆时,集合仍有可能被更改。它仍然是非线程安全的。
    【解决方案2】:

    没有这样的操作。你能做的最好的就是

    lock(collection){ foreach (object o in collection){ ... } }

    【讨论】:

      【解决方案3】:

      您的问题是枚举不允许 IList 更改。这意味着您在浏览列表时必须避免这种情况。

      我想到了几种可能性:

      • 克隆列表。现在每个枚举器都有自己的副本可以处理。
      • 序列化对列表的访问。使用锁确保在枚举时没有其他线程可以修改它。

      或者,您可以编写自己的 IList 和 IEnumerator 实现,以实现所需的并行访问。不过,恐怕这并不简单。

      【讨论】:

        【解决方案4】:
        ICollection MyCollection;
        // Instantiate and populate the collection
        lock(MyCollection.SyncRoot) {
          // Some operation on the collection, which is now thread safe.
        }
        

        来自MSDN

        【讨论】:

          【解决方案5】:

          你会发现这是一个非常有趣的话题。

          最好的方法依赖于 ReadWriteResourceLock,由于所谓的 Convoy 问题,它曾经存在很大的性能问题。

          我发现处理该主题的最佳文章是 Jeffrey Richter 的 this one,它公开了自己的高性能解决方案方法。

          【讨论】:

          • 您的链接已失效
          【解决方案6】:

          所以要求是:在添加和删除元素的同时,您需要在不复制的情况下通过 IList 进行枚举。

          你能澄清一些事情吗?插入和删除是否只发生在列表的开头或结尾? 如果可以在列表中的任何点进行修改,那么当在枚举当前元素的附近或位置移除或添加元素时,枚举应该如何表现?

          这当然可以通过创建一个可能具有整数索引的自定义 IEnumerable 对象来实现,但前提是您可以控制对 IList 对象的所有访问(用于锁定和维护枚举的状态)。但在最好的情况下,多线程编程是一项棘手的工作,而且这是一个复杂的问题。

          【讨论】:

            【解决方案7】:

            Forech 依赖于集合不会改变的事实。如果您想遍历一个可以更改的集合,请使用正常的 for 构造并准备好非确定性行为。锁定可能是一个更好的主意,具体取决于您在做什么。

            【讨论】:

            • 如果 .net 定义了一些具有良好语义的集合,如果它们在枚举期间被更改(例如,在枚举的生命周期中存在的每个项目将只返回一次,并且在枚举期间存在的项目)枚举的一部分将任意返回零次或一次)。不幸的是,我什么都不知道。几乎所有集合类型的正常行为都是在集合被编辑时抛出异常;一个有用的例外是 VB6 风格的“Collection”类;不幸的是,这在其他方面非常有限。
            【解决方案8】:

            简单的索引数据结构(如链表、b 树或哈希表)的默认行为是按从第一个到最后一个的顺序进行枚举。在迭代器已经超过该点之后在数据结构中插入一个元素或插入一个迭代器一旦到达就会枚举的元素不会导致问题,并且应用程序可以检测到这样的事件并在以下情况下进行处理应用程序需要它。为了检测集合中的变化并在枚举过程中抛出错误,我只能想象某人的(坏)想法,即做他们认为程序员想要的事情。事实上,微软已经修复了他们的集合以正常工作。他们在 .NET 4.0 中调用了他们闪亮的新的完整集合 ConcurrentCollections (System.Collections.Concurrent)。

            【讨论】:

            • 你快要冒犯了。也许您可以编辑您的答案,使其不那么令人不快。
            • 我同意您对 Microsoft 的观点的反感,即如果集合被修改,iEnumerable 的合同应该要求它抛出。如果我有我的 druthers,iEnumerable 将没有重置方法,但会有一个派生接口支持重置并保证多次通过将匹配或抛出,以及第二个派生接口如果在与枚举器相同的线程,但会遵守合理的语义(例如,在整个枚举中存在的每个项目都应该只返回一次)。
            • 顺便说一句,我也会让 iEnumerable 包含一个 Snapshot 方法,这种方法不能总是单独使用 iEnumerable 提供(因为在不锁定某些东西的情况下可能无法完成快照,并且 iEnumerable不会提供这样做的方法)。
            【解决方案9】:

            将列表包装在一个锁定对象中以进行读写。如果你有一个合适的锁,你甚至可以一次迭代多个读取器,它允许多个并发读取器但也允许单个写入器(当没有读取器时)。

            【讨论】:

              【解决方案10】:

              我最近花了一些时间对大型应用程序进行多线程处理,并且在处理跨线程共享的对象列表时遇到了很多问题。

              在许多情况下,您可以使用旧的 for 循环并立即将对象分配给一个副本以在循环内使用。请记住,写入列表对象的所有线程都应该写入对象的不同数据。否则,请按照其他贡献者的建议使用锁或副本。

              例子:

              foreach(var p in Points)
              {
                  // work with p...
              }
              

              可以替换为:

              for(int i = 0; i < Points.Count; i ++)
              {
                 Point p = Points[i];
                 // work with p...
              }
              

              【讨论】:

                【解决方案11】:

                这是我最近不得不处理的事情,对我而言,这实际上取决于您对列表的处理方式。

                如果您需要在某个时间点使用列表(考虑到当前其中的元素数量)并且另一个线程只能添加到列表的末尾,那么也许您只需切换到 FOR 循环柜台。在您抓住计数器时,您只会看到列表中的 X 个元素。您可以浏览列表(而其他人正在添加到它的末尾)。 . .不应该引起问题。

                现在,如果列表需要由其他线程从列表中取出或由其他线程清除,那么您需要实现上述锁定机制之一。此外,您可能想查看一些较新的“并发”集合类(尽管我不相信它们实现 IList - 所以您可能需要重构字典)。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-08-13
                  • 1970-01-01
                  • 2010-10-05
                  • 1970-01-01
                  • 2014-09-09
                  • 2012-10-17
                  相关资源
                  最近更新 更多