考慮一個簡單的Registry
類被多個線程訪問方法使用鎖時:LINQ延遲執行中返回IEnumerable的
public class Registry
{
protected readonly Dictionary<int, string> _items = new Dictionary<int, string>();
protected readonly object _lock = new object();
public void Register(int id, string val)
{
lock(_lock)
{
_items.Add(id, val);
}
}
public IEnumerable<int> Ids
{
get
{
lock (_lock)
{
return _items.Keys;
}
}
}
}
和典型用法:
var ids1 = _registry.Ids;//execution deferred until line below
var ids2 = ids1.Select(p => p).ToArray();
這個類不是線程安全的,因爲它是可能收到System.InvalidOperationException
收藏被修改;枚舉操作可能不會執行。
時IDS2分配如果作爲_items.Keys
執行不鎖下進行的線程調用Register
!
這可以通過修改Ids
返回一個IList
予以糾正:
public IList<int> Ids
{
get
{
lock (_lock)
{
return _items.Keys.ToList();
}
}
}
但你失去了很多的延遲執行的「善」的,例如
var ids = _registry.Ids.First(); //much slower!
所以,
1)在這種特殊情況下,是否有任何線程安全的選項,涉及IEnumerable
2)什麼是一些最佳實踐時wo與IEnumerable
和鎖?
問題同時使用不返回值的屬性ID,它指的是Dictionary.Keys,它是一個共享實例(對同一字典實例的每次調用都返回相同的密鑰集合實例)。或者像第二個實現那樣創建一個副本,或者使用ConcurrentDictionary類 – Polity 2012-02-01 13:15:12
只需使用'ConcurrentDictionary',然後轉儲鎖定:http://msdn.microsoft.com/en-us/library/dd287191.aspx –
LukeH
2012-02-01 13:15:35
請注意,您在你的第一個片段中重新查看字典的關鍵集合,這不是一個流式序列。這與你的List版本在這方面沒有多大區別。 – 2012-02-01 13:17:56