我有一些数据库操作要执行,我尝试使用 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 语法,但我不得不将第二个版本用于演出。
有人可以解释表演的重大差异吗?
答案 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()
。