- 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/
我在我的网络应用程序中创建了一个 ConcurrrentDictionary 作为应用程序对象。它在 session 之间共享。 (基本上用作存储库。) 有时,任何可用的 session 都会将新项目
ConcurrentDictionary.TryUpdate 方法需要与具有指定键的元素的值进行比较的 comparisonValue。但是如果我想做这样的事情: if (!_store.TryGet
如果我有一个 ConcurrentDictionary例如,我是否使用 Count 有关系吗?属性(property)或LINQ的Any() ?我宁愿写 dict.Any()而不是 dict.Coun
我正在使用并发字典来存储大约200万条记录,并且想知道如何初始化字典的并发级别。 MSDN页面的示例代码中具有以下注释: concurrencyLevel越高,可以在ConcurrentDiction
我正在使用 ConcurrentDictionary 来存储日志行,当我需要向用户显示它们时,我调用 ToList()生成一个列表。但奇怪的是,一些用户在列表中最先收到最近的行,而从逻辑上讲,他们应该
我发现这个 C# 扩展将 GetOrAdd 转换为 Lazy,我想对 AddOrUpdate 做同样的事情。 有人可以帮我将其转换为 AddOrUpdate 吗? public static cla
我对并发字典如何锁定他们的资源感到困惑。 例如,如果我运行一个方法来迭代字典中的每个项目并在一个线程中编辑它的值,然后我尝试从另一个线程读取一个键的值: 第二个线程会访问字典的快照吗? 如果没有,如果
我找不到有关 ConcurrentDictionary 类型的足够信息,因此我想在这里询问一下。 目前,我使用字典来保存由多个线程(来自线程池,因此没有确切数量的线程)不断访问的所有用户,并且它具有同
我在 Azure 应用服务上使用 MVC 5、ASP.NET 4.7 我正在使用 ConcurrentDictionary 对象来保存数据,以保存对数据源的多次调用。 关于其行为的几个问题: 1) 填
我想使用 ConcurrentDictionary 来检查之前是否添加了这个数据键,但看起来我仍然可以添加之前添加的键。 代码: public class pKeys {
我正在按照教程构建聊天客户端和服务器,现在我遇到了以下错误: Inconsistent accessibility: field type 'System.Collections.Concurrent
我正在考虑将类(单例)与使用 ConcurrentDictionary 实现的集合一起使用。此类将用作缓存实现 (asp.net/wcf)。 您如何看待从此类中显式公开这些集合与仅公开例如3 种方法(
有一个并发字典,它从不同的来源收集信息,每分钟刷新一次并将收集的数据传递给另一个处理程序。 var currentDictionarySnapshot = _currentDictionary; _c
我在 static 类中有一个静态 ConcurrentDictionary。在该类的静态构造函数中,我通过 Task.Run 调用一个私有(private)方法来无限循环遍历字典并删除已过期的项目,
我在 StackOverflow 上阅读了以下文章:ConcurrentBag - Add Multiple Items?和 Concurrent Dictionary Correct Usage但答
我存储这个类 public class Customer { public string Firstname { get; set; } public string Lastname
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 关于您编写的代码问题的问题必须在问题本身中描述具体问题 — 并且包括有效代码 以重现它。参见 SSC
我正在编写一些使用反射的代码。所以我试图将昂贵的反射处理缓存到 ConcurrentDictionary 中。但是,我想对并发字典应用限制,以防止存储旧的和未使用的缓存值。 我做了一些研究以了解如何限
好吧,所以我遇到了一个奇怪的小问题,坦率地说,我没有想法。我想把它扔出去看看我是否遗漏了我做错的东西,或者 ConcurrentDictionary 是否工作不正常。这是代码: (缓存是一个包含静态
我正在尝试使用 ConcurrentDictionary 来帮助完成过滤任务。 如果一个数字出现在列表中,那么我想将一个条目从一个字典复制到另一个。 但这部分的 AddOrUpdate 是不对的 -
我是一名优秀的程序员,十分优秀!