Parallel.ForEach vs Task.Factory.StartNew

sta*_*ser 260 c# parallel-extensions task-parallel-library c#-4.0

下面的代码片段有什么区别?两个都不会使用线程池线程吗?

例如,如果我想为集合中的每个项目调用一个函数,

Parallel.ForEach<Item>(items, item => DoSomething(item));

vs

foreach(var item in items)
{
  Task.Factory.StartNew(() => DoSomething(item));
}
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 293

第一个是更好的选择.

Parallel.ForEach在内部使用a Partitioner<T>将您的集合分发到工作项中.它不会为每个项目执行一项任务,而是批量处理以降低所涉及的开销.

第二个选项将Task在您的集合中为每个项目安排一个.虽然结果将(几乎)相同,但这将带来远远超过必要的开销,特别是对于大型集合,并导致整体运行时间变慢.

仅供参考 - 如果需要,可以通过对Parallel.ForEach使用适当的重载来控制使用的分区程序.有关详细信息,请参阅MSDN上的自定义分区程序.

在运行时,主要的区别是第二个将异步.这可以使用Parallel.ForEach通过执行以下操作进行复制:

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
Run Code Online (Sandbox Code Playgroud)

通过这样做,您仍然可以利用分区程序,但在操作完成之前不要阻塞.

  • IIRC,Parallel.ForEach完成的默认分区还考虑了可用的硬件线程数,从而使您不必计算出要启动的最佳任务数.查看Microsoft的[并行编程模式](http://www.microsoft.com/downloads/en/details.aspx?FamilyID=86b3d32b-ad26-4bb8-a3ae-c1637026c3ee)文章; 它对所有这些东西都有很好的解释. (8认同)
  • @Mal:排序......这实际上不是分区程序,而是TaskScheduler的工作.默认情况下,TaskScheduler使用新的ThreadPool,现在可以很好地处理这个问题. (2认同)

Shi*_*ala 85

我做了一个小实验,用"Parallel.For"和一个带有"Task"对象的方法运行一个方法"1000000000"次.

我测量了处理器时间,发现Parallel效率更高.Parallel.For将您的任务划分为小工作项,并以最佳方式并行地在所有核上执行它们.虽然创建大量任务对象(FYI TPL将在内部使用线程池)将在每个任务上执行每次执行,从而在框中产生更多压力,这从下面的实验中可以看出.

我还创建了一个小视频,解释了基本的TPL,并演示了与正常任务和线程相比,Parallel.For如何更有效地利用您的核心http://www.youtube.com/watch?v=No7QqSc5cl8.

实验1

Parallel.For(0, 1000000000, x => Method1());
Run Code Online (Sandbox Code Playgroud)

实验2

for (int i = 0; i < 1000000000; i++)
{
    Task o = new Task(Method1);
    o.Start();
}
Run Code Online (Sandbox Code Playgroud)

处理器时间比较

  • 对不起,我的错,我应该澄清一下.我的意思是在循环中创建任务到1000000000.开销是不可想象的.更不用说Parallel一次不能创建超过63个任务,这使得它在这种情况下更加优化. (3认同)

Sog*_*ger 17

Parallel.ForEach将优化(甚至可能不启动新线程)并阻塞直到循环结束,Task.Factory将为每个项显式创建一个新的任务实例,并在它们完成之前返回(异步任务).Parallel.Foreach效率更高.


小智 10

在我看来,最现实的情况是任务需要繁重的操作才能完成.Shivprasad的方法更侧重于对象创建/内存分配而不是计算本身.我做了一个调用以下方法的研究:

public static double SumRootN(int root)
{
    double result = 0;
    for (int i = 1; i < 10000000; i++)
        {
            result += Math.Exp(Math.Log(i) / root);
        }
        return result; 
}
Run Code Online (Sandbox Code Playgroud)

执行此方法大约需要0.5秒.

我用并行调用了200次:

Parallel.For(0, 200, (int i) =>
{
    SumRootN(10);
});
Run Code Online (Sandbox Code Playgroud)

然后我用老式的方式称它为200次:

List<Task> tasks = new List<Task>() ;
for (int i = 0; i < loopCounter; i++)
{
    Task t = new Task(() => SumRootN(10));
    t.Start();
    tasks.Add(t);
}

Task.WaitAll(tasks.ToArray()); 
Run Code Online (Sandbox Code Playgroud)

第一个案例在26656ms完成,第二个案例在24478ms完成.我重复了很多次.每次第二种方法都快得多.