2011-07-17 22 views
1

想象一下,在下面的类中,一个线程获取IEnumerable对象并开始对元素进行迭代。在迭代过程中,另一个线程出现并通过Add-method向library_entries添加一个新条目。 “收集是否会被修改” - 在迭代中抛出异常?或者,锁会阻止添加元素直到迭代完成?或者既不?线程和IEnumerable; “Collection was Modified”-exception

谢谢!

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)); 
    } 
} 
+1

你可以看看.net 4的并发集合来检查它们中的一个是否对你想要做的事情有用(我不确定你想要什么)。 – CodesInChaos

回答

0

静态GetEntries方法不能在静态library_entries收集执行任何锁=>它不是线程安全的,从多个线程上的任何并发呼叫可能会中断。事实上,你已经锁定了Add方法,但是枚举并不是一个线程安全的操作,因此如果你打算同时调用GetEntries方法,你必须锁定它。此外,因为此方法返回IEnumerable<T>它不会在实际列表中执行任何操作,直到您开始枚举可能在GetEntries方法之外的内容为止。所以你可以在LINQ链接结尾添加一个.ToList()调用,然后锁定整个操作。

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++; 
    } 
} 

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

+0

通过返回一份副本,您将失去延期执行的任何优势。 – Femaref

+0

有人会以这种方式失去延迟执行,并且可能导致昂贵的分配。或者可以设计一些线程安全的迭代器,但我不确定这是个好主意。 – CodesInChaos

0

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

4

不,您不会得到异常,您正在使用Linq查询。它更糟糕,它将无法预料地失败。最典型的结果是同一个项目被枚举两次,尽管在Add()调用期间列表重新分配其内部存储时,任何事情都是可能的,包括IndexOutOfRangeException。每周一次,给予或服用。

调用GetEntries()并使用枚举数的代码也必须获取该锁。锁定Where()表达式不够好。除非您创建列表的副本。