- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我的工作假设是,当与 System.Collections.Concurrent 集合(包括 ConcurrentDictionary)一起使用时,LINQ 是线程安全的。
(其他 Overflow 帖子似乎同意:link)
但是,对 LINQ OrderBy 扩展方法的实现的检查表明,对于实现 ICollection 的并发集合子集,它似乎不是线程安全的(例如 < strong>ConcurrentDictionary).
OrderedEnumerable GetEnumerator ( source here ) 构造一个 Buffer 结构 ( source here ) 的实例,它试图转换集合到 ICollection(ConcurrentDictionary 实现),然后执行 collection.CopyTo 并使用一个初始化为集合大小的数组。
因此,如果 ConcurrentDictionary(在本例中为具体的 ICollection)在 OrderBy 操作期间增加了大小,在初始化数组和复制到数组之间,此操作会抛出。
下面的测试代码显示了这个异常:
(注意:我很欣赏在线程安全的集合上执行 OrderBy,它在你下面发生变化并没有那么有意义,但我认为它不应该抛出)
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Program
{
class Program
{
static void Main(string[] args)
{
try
{
int loop = 0;
while (true) //Run many loops until exception thrown
{
Console.WriteLine($"Loop: {++loop}");
_DoConcurrentDictionaryWork().Wait();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static async Task _DoConcurrentDictionaryWork()
{
var concurrentDictionary = new ConcurrentDictionary<int, object>();
var keyGenerator = new Random();
var tokenSource = new CancellationTokenSource();
var orderByTaskLoop = Task.Run(() =>
{
var token = tokenSource.Token;
while (token.IsCancellationRequested == false)
{
//Keep ordering concurrent dictionary on a loop
var orderedPairs = concurrentDictionary.OrderBy(x => x.Key).ToArray(); //THROWS EXCEPTION HERE
//...do some more work with ordered snapshot...
}
});
var updateDictTaskLoop = Task.Run(() =>
{
var token = tokenSource.Token;
while (token.IsCancellationRequested == false)
{
//keep mutating dictionary on a loop
var key = keyGenerator.Next(0, 1000);
concurrentDictionary[key] = new object();
}
});
//Wait for 1 second
await Task.Delay(TimeSpan.FromSeconds(1));
//Cancel and dispose token
tokenSource.Cancel();
tokenSource.Dispose();
//Wait for orderBy and update loops to finish (now token cancelled)
await Task.WhenAll(orderByTaskLoop, updateDictTaskLoop);
}
}
}
OrderBy 抛出异常导致了几个可能的结论之一:
1) 我关于 LINQ 对并发集合线程安全的假设是不正确的,只有在 LINQ 查询期间不发生变化的集合(无论是否并发)上执行 LINQ 才是安全的
2) LINQ OrderBy 的实现存在一个错误,实现尝试将源集合转换为 ICollection 并尝试执行集合复制是不正确的(并且它应该只需通过迭代 IEnumerable 的默认行为即可)。
3) 我误解了这里发生的事情......
非常感谢您的想法!
最佳答案
在任何地方都没有说明 OrderBy
(或其他 LINQ 方法)应该始终使用源 IEnumerable
的 GetEnumerator
或者它应该是线程安全的在并发集合上。 promise 的就是这个方法
Sorts the elements of a sequence in ascending order according to a key.
ConcurrentDictionary
在某种全局意义上也不是线程安全的。对于对其执行的其他操作,它是线程安全的。更重要的是,文档说
All public and protected members of ConcurrentDictionary are thread-safe and may be used concurrently from multiple threads. However, members accessed through one of the interfaces the ConcurrentDictionary implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.
因此,您的理解是正确的(OrderBy
会看到您传递给它的 IEnumerable
实际上是 ICollection
,然后会得到该集合的长度,分配该大小的缓冲区,然后将调用 ICollection.CopyTo
,这当然对任何类型的集合都不是线程安全的),但这不是 OrderBy
中的错误因为 OrderBy
和 ConcurrentDictionary
都没有 promise 你的假设。
如果您想在 ConcurrentDictionary
上以线程安全的方式执行 OrderBy
,您需要依赖 promise 线程安全的方法。例如:
// 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
),您可以使用Select(c => c)
,在这种情况下它会起作用,但随后我们再次处于没有实际意义的领域,并依靠某些东西在未 promise 的情况下安全使用(Select
也不会总是枚举您的集合。如果集合是数组或列表 - 它会快捷方式并改用索引器)。所以你可以像这样创建扩展方法:
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();
在这种情况下,我们强制枚举 ConcurrentDictionary
(我们知道它在文档中是安全的),然后将其传递给 OrderBy
,因为知道它不会对此造成任何伤害纯 IEnumerable
。请注意,正如 mjwills 在评论中正确指出的那样,这与 ToArray
并不完全相同,因为 ToArray
会生成快照(锁定集合以防止在构建数组时进行修改)和 Select
\yield
不获取任何锁(因此可能会在枚举进行时添加\删除项目)。尽管我怀疑在执行问题中描述的事情时它是否重要 - 在这两种情况下 OrderBy
完成后 - 你不知道你的订购结果是否反射(reflect)了当前的收集状态。
关于c# - 与 ConcurrentDictionary<Tkey, TValue> 一起使用时 C# LINQ OrderBy 线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47630824/
我有一个 Enumerable> .我想创建一个 bool TryGetValue(TKey, out TValue)它的扩展方法就像它在 Dictionary 中可用一样. 我试过了 public
我已经在谷歌上搜索了 Range 参数的作用的解释,但一无所获,所以我相信你会解释使用 It.IsInRange(TValue from, TValue to, Range rangeKind) 时设
//ModelFor(person =>person.Name); public void ModelFor( Expression> expression) { //Result s
作为MSDN says ConcurrentDictionary Class 表示一个线程安全的键值对集合,可以被多个线程同时访问。 但据我所知,System.Collections.Concurre
我有以下字典: IDictionary> myDictionary 我想将字典中的所有值作为 IList 获取.... 只是为了添加一些关于我是如何陷入这种情况的背景...... 我有一个方法可以获取
这似乎是这个 question 的副本,它会问“SortedList 和 SortedDictionary 有什么区别?”不幸的是,答案只是引用了 MSDN 文档(其中明确指出两者之间存在性能和内存使
我正在尝试创建自定义 ReadOnlyDictionary对于.NET 4.0。方法是保持私有(private) Dictionary对象以及标志以确定是否允许添加/删除和项目分配。 这很好用,但我想
我想这并不重要,我只是好奇。 如果字典和查找之间的区别是一个是一对一的,另一个是一对多的,那么不会通过另一个更具体/派生的版本来字典吗? 查找是键/值对的集合,其中键可以重复。字典是键/值对的集合,其
我想在给定 Dictionary 的情况下获取 TKey 和 TValue 的类型类型。 例如。如果类型是 Dictionary我想知道如何获得keyType = typeof(Int32) 和val
我想在给定 Dictionary 的情况下获取 TKey 和 TValue 的类型类型。 例如。如果类型是 Dictionary我想知道如何获得keyType = typeof(Int32) 和val
好的 - 所以我知道构建一个提供功能的工厂方法很简单;但鉴于 Dictionary是 IEnumerable> ,它不应该有一个等价于例如List 的Ctor吗?的ctor(IEnumerable r
我有一个返回 IDictionary > 的函数. 我有另一个函数需要 IDictionary > . 我需要将第一个函数的返回传递给第二个函数。 编译器不想将第一个隐式转换为第二个。那么如何在 O(
我有一个字典如下: public enum Role { Role1, Role2, Role3, } public enum Action { Action1, Action2, Action3,
我认为转换 IDictionary> 相当简单反对 IDictionary> , 但是 var val = (IDictionary>)Value; 抛出 System.InvalidCastExce
我正在努力处理一段简单的代码,即使它很简单,我也找不到解决方案。在一部分上,我有一个名为这样的事件 OnReadMessageParameter(Self, aName, aTypeInfo, poi
我知道当我转换数据类型时,Variants 的速度很慢,即使我只是简单地添加两个保存整数的变体值。我还发现了另一个类似的东西,看起来像变体,称为 TValue。 他们有什么区别? 我正在考虑使用 TS
我在调试时无法检查 TValue 变量的字符串值。悬停菜单不显示字符串值,甚至将其添加到监视列表似乎也很棘手。 给定一个非常基本的示例控制台应用程序,例如 program Project1; uses
我是 C# 的新手,有 Ruby 背景。我还有很多东西要学,这就是为什么我要问以下问题: 目标:1)我想创建一个以字符串为键和我想要的任何对象类型作为值的字典。像这样: Dictionary 2) 但
我需要使用 RTTI 遍历一个具有复杂结构的类。该类有几个我也想迭代的记录成员。 TRTTIHelpers = class public class function DoGetValu
我遇到了一个奇怪的问题,涉及重命名 TValue 数组类型,例如; TValueArray1 := TArray ; TValueArray2 := Array of TValue; 我的意思是说他们
我是一名优秀的程序员,十分优秀!