并发ToLookup()转换?

时间:2016-01-08 22:38:23

标签: 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

1 个答案:

答案 0 :(得分:4)

这似乎是一个简单的问题,但正如其他(有问题的)答案所显示的那样,解决方案并非真正无足轻重。

现有答案的问题

就目前而言,两个提出的解决方案都会导致创建某种字典,每次在任何给定键上枚举IEnumerable<Contact>时,通过枚举和过滤从头开始重新创建过滤的IEnumerable<Contact>原来的收藏品。基本上,您在字典中存储的是 logic 以获取所需的已过滤的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>>作为您的字典类型(您可以在上面的示例中替换字典类型,它仍然可以按预期编译和工作) - 只需确保您只添加实体化< / em>,最好是在构建字典时将不可变的集合添加到字典中。

选择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>仍然适用。