Parallel.ForEach缺少项目

时间:2014-07-23 10:08:43

标签: c# .net parallel.foreach

我已经关注了代码:

HttpContext httpContext = HttpContext.Current;
RequestContext currentContext = RequestContextManager.CurrentContext;
ILifetimeScope currentSessionScope = PlatformContext.LifeTimeScope;

ConcurrentQueue<Exception> exceptions = new ConcurrentQueue<Exception>();
ConcurrentBag<ParallelCalculateObj> forEachResult = new ConcurrentBag<ParallelCalculateObj>();
ConcurrentBag<ParallelCalculateObj> testForEachPassResult = new ConcurrentBag<ParallelCalculateObj>();

ParallelLoopResult loopResult = Parallel.ForEach(applications, () =>
{
    HttpContext.Current = httpContext;
    RequestContextManager.SetCustomCurrentContext(currentContext);
    PlatformContext.LifeTimeScope = currentSessionScope;
    return new ParallelCalculateObj();
}, (application, pls, localObj) =>
{
    try
    {
        // some code
    }
    catch (Exception e)
    {
        exceptions.Enqueue(e);
    }
    testForEachPassResult.Add(localObj);
    return localObj;
}, forEachResult.Add);

其中applications.Count = 3。执行上面的代码后,我得到forEachResult.Count = 2testForEachPassResult.Count = 3

为什么forEachResult集合不包含所有元素? 没有例外,ParallelLoopResult.IsCompleted = true

解决我的问题可能有帮助的一件事是这三个项目是在两个线程下运行的:

  1. Item01 - &gt; Thread.CurrentThread.ManagedThreadId是14
  2. Item02 - &gt; Thread.CurrentThread.ManagedThreadId是10
  3. Item03 - &gt; Thread.CurrentThread.ManagedThreadId是14

3 个答案:

答案 0 :(得分:6)

我认为你以错误的方式使用Parallel.ForEach

您正在使用具有本地状态的重载。此本地状态对于分区/线程是唯一的,但并非每个迭代都具有唯一的本地状态。

考虑将输入列表划分为 N 分区。然后有 N 本地状态。最后一步,您将这些 N 本地状态合并到最终值中。通常, N 将小于列表中的项目数,除非您使用一个更具体的重载,否则TPL将确定列表的分区方式。

由于您显然希望使用每次迭代的结果填充一些列表,因此本地状态也应该是一个列表,其中包含该特定分区的每次迭代的结果。对于最后的操作,您将所有列表合并为一个列表:

Parallel.ForEach(
    applications,
    () => new List<ParallelCalculateObj>(),
    (application, pls, localObj) =>
    {
        // do something
        var obj = new ParallelCalculateObj { /* data of the iteration */ };
        localObj.Add(obj);
        return localObj;
    },
    localObj =>
    {
        foreach (var result in localObj)
        {
            forEachResult.Add(result);
        }
    });

请注意,如果您这样做,那么forEachResult中的值顺序将与applications中的项目顺序不对应。如果你想要那么你必须使用ParallelLoopState类的索引。

答案 1 :(得分:1)

尝试

  lock(testForEachPassResult){
     testForEachPassResult.Add(localObj);
   }

最有可能在添加元素时,您没有列表的最新状态。请记住,列表可以从另一个线程同时添加另一个元素。因此,当它被更改时,您将新项目添加到旧版本的testForEachPassResult。

如果您锁定列表,则所有其他线程将等待列表解锁。

答案 2 :(得分:0)

有3种方法,其中两种已经提到。

(1)每个循环创建一个集合,并将其结果添加到此任务结果集合中。完成后,将合并所有任务结果。我会将其用作默认值,因为如果避免了锁争用。

(2)在所有任务之外创建一个公共集合。所有任务都将写入该集合,但是对该集合的写入(添加项)必须与锁同步,因为它不是原子的。如果仅要处理几百个项目,但每个项目的处理时间为一秒或更长,我将使用此方法。在这种情况下,锁定时间可以忽略不计。

(3)在所有任务之外创建一个数组,并为每个任务分配一个写入结果的区域。即使所有任务都写入同一阵列,它们写入的内存位置也不同。因此不需要同步。

示例(3)

// Source must be array or IList.
var source = Enumerable.Range(0, 100000).ToArray();

// Partition the entire source array.
var rangePartitioner = Partitioner.Create(0, source.Length);

double[] results = new double[source.Length];

// Loop over the partitions in parallel.
Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
    // Loop over each range element without a delegate invocation.
    for (int i = range.Item1; i < range.Item2; i++)
    {
        results[i] = source[i] * Math.PI;
    }
});

代码来自:https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/custom-partitioners-for-plinq-and-tpl#configuring-static-range-partitioners-for-parallelforeach