wal*_*wal 13 c# linq thread-safety
考虑一个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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
和典型用法:
var ids1 = _registry.Ids;//execution deferred until line below
var ids2 = ids1.Select(p => p).ToArray();
Run Code Online (Sandbox Code Playgroud)
这个类不是线程安全的,因为它可以接收 System.InvalidOperationException
收集被修改; 枚举操作可能无法执行.
如果另一个线程调用Register,则分配ids2 _items.Keys,因为锁定下没有执行!
这可以通过修改Ids返回IList:
public IList<int> Ids
{
get
{
lock (_lock)
{
return _items.Keys.ToList();
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,例如,你失去了许多延迟执行的"善"
var ids = _registry.Ids.First(); //much slower!
Run Code Online (Sandbox Code Playgroud)
因此,
1)在这种特殊情况下,是否存在涉及的任何线程安全选项IEnumerable
2)使用IEnumerable和锁定时有哪些最佳实践?
Ids访问您的属性时,无法更新字典,但是没有什么可以阻止字典在LINQ延迟执行IEnumerator<int>它的同时进行更新Ids.
只要字典的更新也被锁定,调用.ToArray()或.ToList()在Ids属性内部和锁内将消除线程问题.在不锁定字典更新的情况下ToArray(),仍然可以在内部引起竞争条件.ToArray()并.ToList()在IEnumerable上操作.
为了解决这个问题,您需要获取ToArray锁定内部的性能,加上锁定字典更新,或者您可以创建一个IEnumerator<int>本身是线程安全的自定义.只有通过控制迭代(并在该点锁定),或通过锁定数组副本才能实现这一目标.
下面是一些例子:
只是用ConcurrentDictionary<TKey, TValue>.
请注意,它ConcurrentDictionary<TKey, TValue>.GetEnumerator是线程安全的:
从字典返回的枚举器可以安全地与字典的读写一起使用
| 归档时间: |
|
| 查看次数: |
1534 次 |
| 最近记录: |