并行ForEach和ConcurrentBag

时间:2014-09-03 17:59:47

标签: c# multithreading task-parallel-library

我在ConcurrentBag内有Parallel.ForEach个读/写操作。基本上,我需要根据几个属性来检查包中是否存在物体,如果没有匹配,那么我将它添加到包中。这真的非常慢。使用没有锁定的List<>只是一小部分时间。这段代码有什么问题?我最好使用ReaderWriterLockSlim列表锁定吗?我在这里处理大约1,000,000个对象。

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);    
}
}

3 个答案:

答案 0 :(得分:3)

ConcurrentBag<T>并未针对此类情景进行优化。它是使用ThreadLocal<T>实现的,这会使您的特定用例变慢。您反复在许多线程上迭代整个集合。迭代整个集合以检查对象是否存在也很慢。

我建议重载Beneficiary.GetHashCode并使用ConcurrentDictionary<Beneficiary, byte>。字节值可以被忽略,它实际上是一个并发的hashset。

答案 1 :(得分:3)

首先,你所拥有的解决方案根本不是安全的。在您复制副本时,甚至在您执行Any之后但在致电Add之前,可以将项目添加到集合中。您还在进行线性搜索,这根本不会表现良好。使用基于字典的结构可以更快地查找,并且还需要确保您在此处拥有的整个方法在逻辑上是原子的。

你可以做的是使用ConcurrentDictionary并创建一个IEqualityComparer来检查你关心的5个属性,这样你就可以在覆盖重复项时将这些项添加到字典中。

当然,如果创建每个对象实际上需要大量工作,这只是有意义的。如果你所要做的就是获得一系列不同的项目,那么尝试并行化该操作的可能性就不会是一场胜利。如果这基本上只是你所做的事情,那么每个线程需要完成其工作的资源将会出现这样的争论,即你所拥有的实际并行化的数量将非常低,几乎可以肯定比线程开销少的方法会花费你。您可能最好只使用同步Distinct来电。

答案 2 :(得分:2)

您可以使用Tuple作为密钥,并使用ConcurrentDictionary来存储benificiary对象。

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);}
    }
});

parallel.ForEach完成后,您可以使用简单的ForEach访问不同的受益人。

注意:你应该替换&#34;对象&#34;按typeOf ProductType键入。

相关问题