【问题标题】:Parallel.ForEach and foreach loops for BlockingCollection.GetConsumingEnumerable() collection in TaskFactory.TasksTaskFactory.Tasks 中 BlockingCollection.GetConsumingEnumerable() 集合的 Parallel.ForEach 和 foreach 循环
【发布时间】:2011-04-17 16:08:37
【问题描述】:

我已经对这两个循环进行了试验,并注意到即使 Task 的 Action 委托中的常规 foreach 循环应该并行执行,它也不会并行处理元素。但是,如果我用 Parallel.ForEach 替换它,我会看到数据正在跨多个线程并行处理。

代码 1:

Task loadingTask1 = Factory.StartNew(() =>
        {
            foreach (MyOneClass dg in Queue.GetConsumingEnumerable())
            {

                MyOtherClass vl = new MyOtherClass();
                vl.Id = dg.Id;
                vl.PerformTimeConsumingAction();

                OutputQueue.Add(vl);
            }
        });

代码 2:

Task loadingTask2 = Factory.StartNew(() =>
        {
            Parallel.ForEach(Queue.GetConsumingEnumerable(), (dg) =>
            {

                MyOtherClass vl = new MyOtherClass();
                vl.Id = dg.Id;
                vl.PerformTimeConsumingAction();

                OutputQueue.Add(vl);
            });
        });

使用 Console.Write 语句运行时的代码 1 似乎在等待上一个循环完成,直到它抓住下一个循环,但代码 2 确实并行处理多个元素。

我没有正确理解 Task.Action 中的常规 foreach 吗?我认为 .NET 会在负载保证的情况下为任务启动尽可能多的线程,并且 foreach 的每次迭代都将并行处理。

我还尝试将 PLINQ 结果传递给上述代码和观察者相同的行为:常规 foreach 似乎等待上一次迭代完成以开始下一次迭代,即使我使用了 .AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism)指令。

任何见解都将受到高度赞赏。 我知道 OrderingPartitioner 类,可以尝试使用它

【问题讨论】:

  • "即使 Task 的 Action 委托中的常规 foreach 循环应该并行执行" 您从哪里得到这个想法? 一个普通的foreach就是一个普通的foreach。里面没有花哨的平行东西。这就是为什么我们有Parallel.ForEach()
  • 我比较了 Task 的 Action 委托和线性过程中的常规 foreach 性能。在 Task 的动作委托内部,它的执行速度更快,所以我假设线程池正在决定生成多少并行线程以使任务以最佳方式执行。
  • 有多快?我敢打赌,这要么是纯粹的运气,要么是糟糕的基准测试。在任务中运行 foreach 仍然会按顺序运行,因为它仍然是 foreach。 没有魔法。您可以做的是与该 sequential foreach 并行运行。
  • 速度没有翻倍,但明显快了很多。我会说 10 秒 vs 7。你可能是对的,这可能是你提到的那些原因之一。只是想确保,无论您如何实现 foreach,它仍然会等待上一次迭代,即使您循环通过 BlockingCollection(集合已准备好并行)
  • 更新:使用 PLINQ 从 BlockingCollection 获取并行查询,并在生成的并行查询上使用常规 foreach 循环提供了最佳性能,并按顺序保存记录

标签: c# multithreading parallel-processing task-parallel-library


【解决方案1】:

一个常规的 foreach always 按顺序运行它的迭代。在某些情况下,没有魔法可以将其变成并行结构。这就像把你扔进pit of despair,因为这样就很难断言像 foreach 循环这样简单的东西的正确性。幸运的是,C# 的目标之一就是把你扔进成功的坑里:

如果您将 foreach 循环运行在单独的任务上,则所有迭代都会按顺序运行,但您可以在整个 foreach 中并行运行其他代码。

在单独的任务上执行常规 foreach 的流程如下所示:

              |
            __v_
           /    \
other code |    | foreach iteration 1
other code |    | foreach iteration 2
other code |    | foreach iteration 3
           ......
other code |    | foreach iteration n-1
other code |    | foreach iteration n
           v    v

Parallel.Foreach 的执行流程如下所示:

                  |
 _________________v________________
/    /    /    /    \    \    \    \
|1   |2   |3   |....|    |n-2 |n-1 |n
\____\____\____\____/____/____/____/
                  |
                  v

希望这有助于了解正在发生的事情。

【讨论】:

  • 画的真棒! :) 感谢您花时间和解释
  • @Dimitri:来自 Jon Skeet 的博客:msmvps.com/blogs/jon_skeet/archive/2010/09/02/…
  • 现在,有没有办法使用 Parallel.ForEach 保证 BlockingCollection 的先进先出处理?我想我必须实现自定义分区器?
  • @Dimitri如果您想要 FIFO 行为,请改用 ConcurrentQueue。
  • 我使用的是 BlockingCollection,它默认环绕 ConcurrentQueue,不是吗?
猜你喜欢
  • 1970-01-01
  • 2012-01-03
  • 1970-01-01
  • 2013-10-09
  • 2018-11-18
  • 2019-08-21
  • 1970-01-01
  • 1970-01-01
  • 2018-05-13
相关资源
最近更新 更多