2011-07-17 32 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()表達式不夠好。除非您創建列表的副本。