Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/324.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
与ConcurrentDictionary一起使用时,C#LINQ OrderBy threadsafe吗<;Tkey,TValue>;?_C#_.net_Linq_Concurrency - Fatal编程技术网

与ConcurrentDictionary一起使用时,C#LINQ OrderBy threadsafe吗<;Tkey,TValue>;?

与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

我的工作假设是LINQ与System.Collections.Concurrent集合(包括ConcurrentDictionary)一起使用时是线程安全的

(其他溢出帖子似乎也同意:)

然而,对LINQOrderBy扩展方法的实现情况进行的检查表明,对于实现ICollection的并发集合子集(例如ConcurrentDictionary),它似乎不是线程安全的

OrderedEnumerableGetEnumerator()构造一个缓冲区的实例,该结构试图将集合强制转换为一个ICollection(它ConcurrentDictionary实现)然后使用初始化为集合大小的数组执行collection.CopyTo

因此,如果在OrderBy操作期间,ConcurrentDictionary(本例中为具体的ICollection)的大小增加,则在初始化数组和复制到数组之间,此操作将抛出

以下测试代码显示此异常:

(注意:我很感激在线程安全的集合上执行OrderBy,而该集合在您下面正在发生变化,这并没有什么意义,但我认为它不应该抛出)

使用系统;
使用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();