与ConcurrentDictionary一起使用时,C#LINQ OrderBy threadsafe吗<;Tkey,TValue>;?
我的工作假设是LINQ与System.Collections.Concurrent集合(包括ConcurrentDictionary)一起使用时是线程安全的 (其他溢出帖子似乎也同意:) 然而,对LINQOrderBy扩展方法的实现情况进行的检查表明,对于实现ICollection的并发集合子集(例如ConcurrentDictionary),它似乎不是线程安全的 OrderedEnumerableGetEnumerator()构造一个缓冲区的实例,该结构试图将集合强制转换为一个ICollection(它ConcurrentDictionary实现)然后使用初始化为集合大小的数组执行collection.CopyTo 因此,如果在OrderBy操作期间,ConcurrentDictionary(本例中为具体的ICollection)的大小增加,则在初始化数组和复制到数组之间,此操作将抛出 以下测试代码显示此异常: (注意:我很感激在线程安全的集合上执行OrderBy,而该集合在您下面正在发生变化,这并没有什么意义,但我认为它不应该抛出)与ConcurrentDictionary一起使用时,C#LINQ OrderBy threadsafe吗<;Tkey,TValue>;?,c#,.net,linq,concurrency,C#,.net,Linq,Concurrency,我的工作假设是LINQ与System.Collections.Concurrent集合(包括ConcurrentDictionary)一起使用时是线程安全的 (其他溢出帖子似乎也同意:) 然而,对LINQOrderBy扩展方法的实现情况进行的检查表明,对于实现ICollection的并发集合子集(例如ConcurrentDictionary),它似乎不是线程安全的 OrderedEnumerableGetEnumerator()构造一个缓冲区的实例,该结构试图将集合强制转换为一个ICollect
使用系统;
使用System.Collections.Concurrent;
使用System.Linq;
使用系统线程;
使用System.Threading.Tasks;
名称空间程序
{
班级计划
{
静态void Main(字符串[]参数)
{
尝试
{
int循环=0;
while(true)//运行许多循环,直到抛出异常为止
{
WriteLine($“循环:{++Loop}”);
_DoConcurrentDictionaryWork().Wait();
}
}
捕获(例外情况除外)
{
控制台写入线(ex);
}
}
专用静态异步任务\u DoConcurrentDictionaryWork()
{
var concurrentDictionary=新建concurrentDictionary();
var keyGenerator=new Random();
var tokenSource=new CancellationTokenSource();
var orderByTaskLoop=Task.Run(()=>
{
var token=tokenSource.token;
while(token.IsCancellationRequested==false)
{
//在循环中保持对并发字典的排序
var orderedPairs=concurrentDictionary.OrderBy(x=>x.Key).ToArray();//在此处引发异常
//…使用有序快照执行更多工作。。。
}
});
var updateDictTaskLoop=Task.Run(()=>
{
var token=tokenSource.token;
while(token.IsCancellationRequested==false)
{
//在循环中不断修改字典
var key=keyGenerator.Next(0,1000);
concurrentDictionary[key]=新对象();
}
});
//等一秒钟
等待任务延迟(时间跨度从秒(1));
//取消并处置令牌
tokenSource.Cancel();
tokenSource.Dispose();
//等待orderBy和update循环完成(现在令牌已取消)
wait Task.WhenAll(orderByTaskLoop,updateDictTaskLoop);
}
}
}
OrderBy抛出异常会导致以下几种可能的结论之一:
1) 我关于LINQ对于并发集合是线程安全的假设是不正确的,只有在LINQ查询期间未发生变化的集合(无论它们是否并发)上执行LINQ才是安全的
2) LINQOrderBy的实现中存在一个bug,实现尝试将源集合强制转换为ICollection并尝试执行集合副本是不正确的(它应该直接进入迭代IEnumerable的默认行为)
3) 我误解了这里发生的事情
非常感谢 任何地方都没有说明
OrderBy
(或其他LINQ方法)应始终使用源IEnumerable
的GetEnumerator
,或者在并发集合上应是线程安全的。所承诺的就是这种方法
根据顺序按升序对序列的元素进行排序
钥匙
ConcurrentDictionary
在某种全局意义上也不是线程安全的。相对于在其上执行的其他操作而言,它是线程安全的。更有甚者,文件上说
ConcurrentDictionary的所有公共和受保护成员
是线程安全的,可以从多个线程并发使用。
但是成员通过
ConcurrentDictionary实现,包括扩展
方法不能保证线程安全,可能需要
由调用方同步
因此,您的理解是正确的(OrderBy
将看到您传递给它的IEnumerable
实际上是ICollection
,然后将获得该集合的长度,分配该大小的缓冲区,然后将调用ICollection.CopyTo
,这当然对任何类型的集合都不是线程安全的),但这不是OrderBy
中的bug,因为无论是OrderBy
还是concurrentdirectionary
都没有承诺过你的假设
如果您想在ConcurrentDictionary
上以线程安全的方式执行OrderBy
,则需要依赖于承诺是线程安全的方法。例如:
// note: this is NOT IEnumerable.ToArray()
// but public ToArray() method of ConcurrentDictionary itself
// it is guaranteed to be thread safe with respect to other operations
// on this dictionary
var snapshot = concurrentDictionary.ToArray();
// we are working on snapshot so no one other thread can modify it
// of course at this point real contents of dictionary might not be
// the same as our snapshot
var sorted = snapshot.OrderBy(c => c.Key);
如果不想分配额外的数组(使用ToArray
),可以使用S
// note: this is NOT IEnumerable.ToArray()
// but public ToArray() method of ConcurrentDictionary itself
// it is guaranteed to be thread safe with respect to other operations
// on this dictionary
var snapshot = concurrentDictionary.ToArray();
// we are working on snapshot so no one other thread can modify it
// of course at this point real contents of dictionary might not be
// the same as our snapshot
var sorted = snapshot.OrderBy(c => c.Key);
public static class Extensions {
public static IEnumerable<T> ForceEnumerate<T>(this ICollection<T> collection) {
foreach (var item in collection)
yield return item;
}
}
concurrentDictionary.ForceEnumerate().OrderBy(c => c.Key).ToArray();