Рассмотрим простой класс Registry
, к которому могут обращаться несколько потоков:
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 назначен , если другой поток вызывает Register
, так как выполнение _items.Keys
не выполняется под блокировкой!
Это можно исправить, изменив Ids
так, чтобы он возвращал IList
:
public IList<int> Ids
{
get
{
lock (_lock)
{
return _items.Keys.ToList();
}
}
}
, но тогда вы потеряете много «добродетелей» отложенного выполнения, например
var ids = _registry.Ids.First(); //much slower!
Итак,
1) В этом конкретном случае существуют какие-либо поточно-ориентированные опции, включающие IEnumerable
2) Каковы некоторые рекомендации при работе с IEnumerable
и блокировками?