Threading and IEnumerable; "Collection Was Modified&quo

本文关键字:Was Modified quo Collection quot and Threading IEnumerable | 更新日期: 2023-09-27 18:02:49

假设在下面的类中,一个线程获得IEnumerable-object并开始遍历元素。在迭代的过程中,另一个线程出现并通过add方法向library_entries中添加一个新条目。在迭代中是否会抛出"Collection被修改"异常?或者锁会阻止元素的添加,直到迭代完成?或不?

谢谢!

public static class Library
{
    private static List<string> library_entries = new List<string>(1000000);
    public static void Add(string entry)
    {
        lock (library_entries)
            library_entries.Add(entry);
    }
    public static IEnumerable<string> GetEntries()
    {
        return library_entries.Where(entry => !string.IsNullOrEmpty(entry));
    }
}

Threading and IEnumerable; "Collection Was Modified&quo

不,你不会得到异常,你正在使用Linq查询。更糟糕的是,它将不可预测地失败。最典型的结果是同一项被枚举两次,尽管当List在Add()调用期间重新分配其内部存储(包括IndexOutOfRangeException)时,任何事情都有可能发生。一周一次,差不多。

调用GetEntries()并使用枚举数的代码也必须获得锁。锁定Where()表达式还不够好。除非你创建了一个列表的副本

锁定根本没有帮助,因为迭代不使用锁定。我建议重写GetEntries()函数以返回一个副本。

public static IEnumerable<string> GetEntries()
{
    lock(lockObj)
    {
        return library_entries.Where(entry => !string.IsNullOrEmpty(entry)).ToList();
    }
}

注意,这会返回一个一致的快照。也就是说,当你迭代时,它不会返回新添加的对象。

我更喜欢锁一个私有对象,它的唯一目的是锁,但由于列表是私有的,这不是真正的问题,只是一个风格问题。

你也可以这样写你自己的迭代器:

int i=0;
bool MoveNext()
{
  lock(lockObj)
  {
      if(i<list.Count)
          return list[i];
      i++;
  }
}

如果这是一个好主意取决于你的访问模式,锁争用,列表的大小,…您可能还需要使用读写锁来避免许多读访问的争用。

静态GetEntries方法不会对静态library_entries集合执行任何锁=>它不是线程安全的,任何来自多个线程的并发调用都可能中断。事实上,您锁定了Add方法,但枚举不是线程安全的操作,所以如果您打算并发地调用GetEntries方法,您也必须锁定它。另外,因为这个方法返回一个IEnumerable<T>,所以它不会对实际列表做任何事情,直到您开始枚举可能在GetEntries方法之外的列表。因此,您可以在LINQ链接的末尾添加.ToList()调用,然后锁定整个操作。

是的,会抛出一个异常-您没有锁定一个普通对象。此外,GetEntries方法中的锁将是无用的,因为该调用会立即返回。锁定必须在迭代时发生