Рассмотрим простой класс 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
и блокировками?