Both PLINQ and Parallel.ForEach()
were primarily designed to deal with CPU-bound workloads, which is why they don't work so well for your IO-bound work. For some specific IO-bound work, there is an optimal degree of parallelism, but it doesn't depend on the number of CPU cores, while the degree of parallelism in PLINQ and Parallel.ForEach()
does depend on the number of CPU cores, to a greater or lesser degree.
Specifically, the way PLINQ works is to use a fixed number of Task
s, by default based on the number of CPU cores on your computer. This is meant to work well for a chain of PLINQ methods. But it seems this number is smaller than the ideal degree of parallelism for your work.
On the other hand Parallel.ForEach()
delegates deciding how many Task
s to run to the ThreadPool
. And as long as its threads are blocked, ThreadPool
slowly keeps adding them. The result is that, over time, Parallel.ForEach()
might get closer to the ideal degree of parallelism.
The right solution is to figure out what the right degree of parallelism for your work is by measuring, and then using that.
Ideally, you would make your code asynchronous and then use some approach to limit the degree of parallelism fro async
code.
Since you said you can't do that (yet), I think a decent solution might be to avoid the ThreadPool
and run your work on dedicated threads (you can create those by using Task.Factory.StartNew()
with TaskCreationOptions.LongRunning
).
If you're okay with sticking to the ThreadPool
, another solution would be to use PLINQ ForAll()
, but also call WithDegreeOfParallelism()
.