想象一下,在下面的类中,一个线程获取IEnumerable对象并开始迭代元素。在迭代过程中,另一个线程出现并通过Add-method向library_entries添加一个新条目。是否会在迭代中抛出“集合被修改”的异常?或者锁定会阻止添加元素,直到迭代完成?或者都不是?
谢谢!
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));
}
}
答案 0 :(得分:4)
不,你不会得到例外,你正在使用Linq查询。更糟糕的是,它将无法预测地失败。最典型的结果是同一项被枚举两次,但是当List在Add()调用期间重新分配其内部存储时,包括IndexOutOfRangeException,任何事情都是可能的。每周一次,给予或接受。
调用GetEntries()并使用枚举器的代码也必须获取锁。锁定Where()表达式不够好。除非您创建列表的副本。
答案 1 :(得分:1)
锁定根本没有帮助,因为迭代不使用锁定。我建议重写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++;
}
}
如果这是一个好主意取决于您的访问模式,锁争用,列表大小,......您可能还想使用读写锁来避免许多读访问的争用。
答案 2 :(得分:0)
静态GetEntries
方法不会对静态library_entries
集合=&gt;执行任何锁定它不是线程安全的,并且来自多个线程的任何并发调用都可能会中断。您已锁定Add方法的事实很好,但枚举不是线程安全操作,因此如果您打算同时调用GetEntries方法,则必须锁定它。此外,因为此方法返回IEnumerable<T>
,所以在您开始枚举哪个可能超出GetEntries
方法之前,它不会对实际列表执行任何操作。因此,您可以在LINQ链的末尾添加.ToList()
调用,然后锁定整个操作。
答案 3 :(得分:0)
是的,会抛出异常 - 您没有锁定常见对象。此外,GetEntries
方法中的锁定将无效,因为该调用立即返回。迭代时会发生锁定。