PLINQ与TPL的表现

时间:2016-05-10 08:31:06

标签: c# task-parallel-library plinq

我有一些数据库操作要执行,我尝试使用 PLINQ

someCollection.AsParallel()
              .WithCancellation(token)
              .ForAll(element => ExecuteDbOperation(element))

我注意到它与以下相比相当缓慢:

var tasks = someCollection.Select(element =>
                                    Task.Run(() => ExecuteDbOperation(element), token))
                          .ToList()

await Task.WhenAll(tasks)

我更喜欢 PLINQ 语法,但我不得不将第二个版本用于演出。

有人可以解释表演的重大差异吗?

3 个答案:

答案 0 :(得分:3)

我相信如果你说多于10000个元素,那么使用PLINQ会更好,因为它不会为你的集合中的每个元素创建任务,因为它在其中使用了分区器。每个任务创建都在其中进行一些开销数据初始化。分区程序将只创建针对当前可用核心优化的任务,因此它将重新使用此任务以处理新数据。您可以在此处详细了解:http://blogs.msdn.com/b/pfxteam/archive/2009/05/28/9648672.aspx

答案 1 :(得分:2)

我认为这是因为创建的线程数量。

在第一个示例中,此数字大致等于计算机的核心数。相比之下,第二个示例将创建与someCollection具有元素一样多的线程。对于通常更有效的IO操作。

Microsoft指南"Patterns_of_Parallel_Programming_CSharp"建议IO操作创建比默认更多的线程(第33页):

var addrs = new[] { addr1, addr2, ..., addrN };
var pings = from addr in addrs.AsParallel().WithDegreeOfParallelism(16)
select new Ping().Send(addr);

答案 2 :(得分:2)

PLINQ和Parallel.ForEach()主要用于处理受CPU限制的工作负载,这就是为什么它们对于IO绑定工作不能很好地工作的原因。对于某些特定的IO绑定工作,存在最佳并行度,但它不依赖于CPU内核的数量,而PLINQ和Parallel.ForEach()中的并行度确实取决于CPU内核的数量,或多或少。

具体来说,PLINQ的工作方式是默认使用固定数量的Task s,具体取决于计算机上的CPU核心数。这意味着适用于PLINQ方法链。但似乎这个数字小于你工作的理想并行度。

另一方面,Parallel.ForEach()代表决定运行Task的{​​{1}}次。只要其线程被阻止,ThreadPool就会慢慢地添加它们。结果是,随着时间的推移,ThreadPool可能会更接近理想的并行度。

正确的解决方案是通过测量然后使用它来确定适合您工作的并行度。

理想情况下,您可以使代码异步,然后use some approach to limit the degree of parallelism fro async code

既然你说你还不能这样做,我认为一个合适的解决方案可能是避免Parallel.ForEach()并在专用线程上运行你的工作(你可以使用ThreadPool来创建它们与Task.Factory.StartNew())。

如果您坚持使用TaskCreationOptions.LongRunning,另一种解决方案是使用PLINQ ThreadPool,还可以拨打ForAll()

相关问题