c# - 并发的ToLookup()转换?

标签 c# .net parallel-processing concurrentdictionary

如何使ToLookup()并发?我有一些这样的代码:

myRepository.GetAllContacts().ToLookup( c => c.COMPANY_ID);


我想要一个类似于以下的结构:

new ConcurrentDicitonary<String,IEnumerable<Contact>>();  // <Company,List of Contacts in company>


因为每个COMPANY_ID可以映射到多个Contact,所以我不能简单地使用ToDictionary

最佳答案

这似乎是一个简单的问题,但是正如其他(有问题的)答案所表明的那样,该解决方案并非真正简单。

现有答案存在的问题

就目前而言,这两个提议的解决方案都会导致创建某种字典,在该字典中,每次您在任何给定键处枚举IEnumerable<Contact>时,都会通过枚举和过滤原始集合从头重新创建已过滤的IEnumerable<Contact>。本质上,您在字典中存储的是获取所需过滤的Contact集合(而不是实际集合)的逻辑。

结果,您将一遍又一遍地枚举原始的IEnumerable<Contact>。从线程安全的角度来看,这是危险的,即使可行,这样做也没有好处,只有开销。

拟议的解决方案

您说对了,因为Lookup/ILookup<TKey, TValue>最好的现成线程安全替代方案似乎是ConcurrentDictionary<TKey, TValue>,其中TValue源自IEnumerable<Contact>。它提供了查找功能的超集,并且如果正确构建,它是线程安全的。在基类库中没有为此可用的扩展方法,因此您可以滚动自己的实现:

IEnumerable<Contact> contacts = GetAllContacts();
ConcurrentDictionary<string, IReadOnlyList<Contact>> dict = new ConcurrentDictionary<string, IReadOnlyList<Contact>>();

foreach (IGrouping<string, Contact> group in contacts.GroupBy(c => c.COMPANY_ID))
{
    if (!dict.TryAdd(group.Key, group.ToArray())) {
        throw new InvalidOperationException("Key already added.");
    }
}


这看起来与其他人提供的内容非常相似,但有一个重要区别:我的词典的TValue是物化的集合(特别是Contact[]冒充为IReadOnlyList<Contact>)。每次将其从字典中拉出并枚举时,它不会从头开始重建。

哦,我也只列举过一次源代码IEnumerable<Contact>-并不是真正改变生活的方法,而是一种很好的用法。

您仍然可以使用ConcurrentDictionary<string, IEnumerable<Contact>>作为字典类型(您可以在上面的示例中替换字典类型,并且仍然可以按预期方式编译和运行)-只需确保仅将具体化的集合(最好是不可变的集合)添加到字典中即可在构建它时。

选择TValue类型:IReadOnlyList<T>的替代

(超出原始问题的范围)

我可以想到的是,IReadOnlyList<T>是最通用的通用准可变集合接口(显然,除了IReadOnlyCollection<T>之外),它向调用者传达该集合已实现且将来不太可能更改。

如果我在自己的代码中使用此代码,则实际上将Contact[]用作字典的TValue进行任何私有和内部调用(出于性能方面的考虑,放弃了“只读”的使用)。对于任何公共API,我都会坚持使用IReadOnlyList<T>或可能的ReadOnlyCollection<T>来强调TValue集合的只读方面。

如果采用外部依赖关系是可行的选择,则还可以将Microsoft的System.Collections.Immutable NuGet添加到项目中,并使用ImmutableDictionary<string, ImmutableArray<Contact>>存储查找。 ImmutableDictionary<TKey, TValue>是不可变的线程安全字典。 ImmutableArray<T>是一种轻量级数组包装器,具有强大的不变性保证,并且还具有通过结构枚举器和某些LINQ方法的重新实现而实现的可靠性能,这些方法完全避免了枚举器分配。

List<T>对于TValue来说不是一个好的选择,因为它是a)可变性,并且b)倾向于分配长度大于List<T>.Count的内部缓冲区数组(除非明确使用List<T>.TrimExcess)。当您将内容存储在字典中时,很有可能会存活一段时间,因此分配内存(例如List<T>确实不打算使用)并不是一个好主意。

编辑

现在,我必须添加:LINQ的Lookup<Tkey, TValue>返回的.NET当前的ToLookup实现实际上似乎是线程安全的。但是,我发现的任何规范都不能保证Lookup<TKey, TValue>上实例方法的线程安全性(MSDN特别声明它们不能保证是线程安全的),这意味着查找线程安全性是一种实现。细节,而不是防弹保证。因此,我上面所说的重新使用ConcurrentDictionary<TKey, TValue>仍然适用。

关于c# - 并发的ToLookup()转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34687284/

相关文章:

C# 如何检查一个类是否实现了泛型接口(interface)?

c# - USB 内存棒隐藏分区

Java Fork-Join 不适用于大型 ArrayList

.net - 一个绑定(bind)中有 2 个属性?

go - 为什么Go使用更多的CPU但不减少计算时间?

c++ - openmp 并行部分基准

c# - 结构图 - 覆盖注册

c# - 如何不断延迟方法的执行

c# - 来自 AXIS 的 SOAP 操作错误

c# - 替换通用列表中的两个字节