Consider a simple Registry
class accessed by multiple threads:
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;
}
}
}
}
and typical usage:
var ids1 = _registry.Ids;//execution deferred until line below
var ids2 = ids1.Select(p => p).ToArray();
This class is not thread safe as it's possible to receive System.InvalidOperationException
Collection was modified; enumeration operation may not execute.
when ids2 is assigned if another thread calls Register
as the execution of _items.Keys
is not performed under the lock!
This can be rectified by modifying Ids
to return an IList
:
public IList<int> Ids
{
get
{
lock (_lock)
{
return _items.Keys.ToList();
}
}
}
but then you lose a lot of the 'goodness' of deferred execution, for example
var ids = _registry.Ids.First(); //much slower!
So,
1) In this particular case are there any thread-safe options that involve IEnumerable
2) What are some best practices when working with IEnumerable
and locks ?
When your Ids
property is accessed then the dictionary cannot be updated, however there is nothing to stop the Dictionary from being updated at the same time as LINQ deferred execution of the IEnumerator<int>
it got from Ids
.
Calling .ToArray()
or .ToList()
inside the Ids
property and inside a lock will eliminate the threading issue here so long as the update of the dictionary is also locked. Without locking both update of the dictionary and ToArray()
, it is still possible to cause a race condition as internally .ToArray()
and .ToList()
operate on IEnumerable.
In order to resolve this you need to either take the performance hit of ToArray
inside a lock, plus lock your dictionary update, or you can create a custom IEnumerator<int>
that itself is thread safe. Only through control of iteration (and locking at that point), or through locking around an array copy can you achieve this.
Some examples can be found below:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With