[英]Threading and IEnumerable; “Collection Was Modified”-exception
想象一下,在下面的 class 中,一個線程獲取 IEnumerable-object 並開始對元素進行迭代。 在迭代過程中,另一個線程出現並通過 Add 方法向 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));
}
}
不,您不會遇到異常,您使用的是 Linq 查詢。 更糟糕的是,它將無法預料地失敗。 最典型的結果是同一個項目被枚舉了兩次,盡管當 List 在 Add() 調用期間重新分配其內部存儲時,任何事情都是可能的,包括 IndexOutOfRangeException。 每周一次,給予或接受。
調用 GetEntries() 並使用枚舉器的代碼也必須獲取鎖。 鎖定 Where() 表達式還不夠好。 除非您創建列表的副本。
鎖定根本沒有幫助,因為迭代不使用鎖定。 我建議重寫GetEntries()
function 以返回副本。
public static IEnumerable<string> GetEntries()
{
lock(lockObj)
{
return library_entries.Where(entry => !string.IsNullOrEmpty(entry)).ToList();
}
}
請注意,這會返回一致的快照。 即迭代時它不會返回新添加的對象。
而且我更喜歡鎖定私有 object ,其唯一目的是鎖定,但由於列表是私有的,這不是真正的問題,只是一個風格問題。
您也可以像這樣編寫自己的迭代器:
int i=0;
bool MoveNext()
{
lock(lockObj)
{
if(i<list.Count)
return list[i];
i++;
}
}
如果這是一個好主意,取決於您的訪問模式、鎖爭用、列表的大小……您可能還想使用讀寫鎖來避免許多讀取訪問的爭用。
static GetEntries
方法不對 static library_entries
集合執行任何鎖定 => 它不是線程安全的,並且來自多個線程的任何並發調用都可能中斷。 您已鎖定 Add 方法這一事實很好,但枚舉不是線程安全操作,因此如果您打算同時調用 GetEntries 方法,您也必須鎖定它。 此外,因為此方法返回一個IEnumerable<T>
,它不會在實際列表上執行任何操作,直到您開始枚舉可能在GetEntries
方法之外的內容。 因此,您可以在 LINQ 鏈接的末尾添加一個.ToList()
調用,然后鎖定整個操作。
是的,會拋出一個異常——你沒有鎖定一個普通的 object。 此外, GetEntries
方法中的鎖是沒有用的,因為該調用會立即返回。 迭代時必須發生鎖定。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.