C# 并行ForEach和ConcurrentBag

C# 并行ForEach和ConcurrentBag,c#,multithreading,task-parallel-library,C#,Multithreading,Task Parallel Library,我有一个ConcurrentBag暴露在Parallel.ForEach中进行读/写操作。基本上,我需要根据几个属性检查包中是否存在对象,如果不匹配,则将其添加到包中。真的,真的很慢。在没有锁的情况下使用列表。这个代码怎么了?使用带有ReaderWriterLockSlim的列表锁定是否更好?我在这里处理大约1000000个对象 var bag = new ConcurrentBag<Beneficiary>(); Parallel.ForEach(cx, _options, li

我有一个
ConcurrentBag
暴露在
Parallel.ForEach
中进行读/写操作。基本上,我需要根据几个属性检查包中是否存在对象,如果不匹配,则将其添加到包中。真的,真的很慢。在没有锁的情况下使用
列表
。这个代码怎么了?使用带有
ReaderWriterLockSlim
的列表锁定是否更好?我在这里处理大约1000000个对象

var bag = new ConcurrentBag<Beneficiary>();

Parallel.ForEach(cx, _options, line =>
{
if (!bag.Any(o =>
       o.WinID == beneficiary.WinID &&
       o.ProductType == beneficiary.ProductType &&
       o.FirstName == beneficiary.FirstName &&
       o.LastName == beneficiary.LastName &&
       o.MiddleName == beneficiary.MiddleName))
{
       bag.Add(beneficiary);    
}
}
var-bag=新的ConcurrentBag();
Parallel.ForEach(cx,_选项,行=>
{
如果(!bag.Any)(o=>
o、 WinID==受益人.WinID&&
o、 ProductType==受益人。ProductType&&
o、 FirstName==受益人。FirstName&&
o、 LastName==受益人。LastName&&
o、 中间名==受益人。中间名)
{
添加(受益人);
}
}
A
ConcurrentBag
没有针对这种类型的场景进行优化。它是使用
ThreadLocal
实现的,这会使您的特定用例变慢。您在多个线程上反复迭代整个集合。迭代整个集合以检查对象是否存在也会变慢


我建议重载
受益人。GetHashCode
并使用
ConcurrentDictionary
。字节值可以忽略,它实际上是一个并发哈希集。

因此,首先,您现有的解决方案根本不是类型安全的。可以在迭代副本时将项添加到集合中,甚至在执行
Any
之后,但在调用
Add
之前,您也在进行线性搜索,这一点都不会很好地执行。您最好使用基于字典的结构,这样可以更快地查找,并且您还需要确保这里的整个方法在逻辑上是原子的

您可以使用
ConcurrentDictionary
并创建一个
IEqualityComparer
,检查您关心的5个属性,这将允许您在覆盖重复项的同时将项目添加到字典中


当然,只有在创建每个对象确实需要大量工作的情况下,这一切才有意义。如果您所要做的只是获取一个不同项的集合,那么尝试并行化该操作很可能不会成功。如果这基本上就是您所做的,那么每个线程所需的工作资源就足够了如果您的实际并行化量非常低,几乎可以肯定,这比线程开销要少。您可能最好只使用同步
Distinct
调用。

您可以使用
元组作为键,使用
ConcurrentDictionary
>存储您的
福利对象

var dict = new ConcurrentDictionary<Tuple<int, object, string>, Beneficiary>();

Parallel.ForEach(cx, _options, line =>
{
    string fullname = string.Join("|", line.FirstName, line.LastName, line.MiddleName);

    Tuple<int, object, string> key = new Tuple<int,object,string>(line.WinID, line.ProductType, fullname);

    //if (!dict.ContainsKey(key)) optional line
    {
        dict.TryAdd(key, line);}
    }
});
var dict=新的ConcurrentDictionary();
Parallel.ForEach(cx,_选项,行=>
{
string fullname=string.Join(“|”,line.FirstName,line.LastName,line.MiddleName);
元组键=新元组(line.WinID、line.ProductType、fullname);
//if(!dict.ContainsKey(key))可选行
{
dict.TryAdd(键,行);}
}
});
一旦
parallel.ForEach
完成,您可以使用简单的
ForEach
访问不同的受益人


注意:您应该用ProductType的类型替换“object”类型。

如果代码实际工作不正常,那么代码的性能就无关紧要。您是希望很快得到不正确的结果,还是希望更慢地得到正确的结果?并不是说没有办法改善这一点,而是“螺纹安全性”你也可以(使用ConcurrentDictionary)使用元组作为key@Seb这有什么帮助?这个包有什么用?重载GetHashCode有什么意义?@Bigdady您需要为
ConcurrentDictionary
覆盖
GetHashCode
,以有效地存储项目。理想情况下,您希望最小化冲突(两个具有相同哈希代码的不同项目)。如果发生哈希冲突时不想进行默认的引用比较,您可能还需要重写受益人。Equals
。@Servy使用元组作为键,您不必重写并实现受益人。GetHashCode此包的用途是什么?它应该用于什么?顺便说一句,这是处理大约1000000个objects。看起来我需要一本字典。@Bigdady是的,我建议如果你真的需要并行完成这项工作,你应该使用一个基于哈希的数据结构,即
ConcurrentDictionary
。正如我在回答中所说的,你正在尝试做的工作不适合并行工作。你可能会让它变得更慢g将其并行化,所以不要。这不会有帮助。为什么要加入名称,而不是使用
Tuple
?您的解决方案更好、更安全,但我有点懒。为了可读性,我尽量避免使用超过3个元素的Tuple。在这种特定情况下,键冲突是不可能的,所以我冒昧加入了fiel谢谢。