Starting from .NET 6, the Parallel.ForEachAsync
method is now part of the standard libraries. The default MaxDegreeOfParallelism
for this method is Environment.ProcessorCount
. This doesn't mean that the number of CPU cores is the optimal concurrency limit for the majority of asynchronous scenarios. It only means that any positive number is preferable to -1
(unlimited), which is the default MaxDegreeOfParallelism
for the non-asynchronous Parallel
APIs. An unlimited default parallelism would cause lots of unintentional DoS attacks, by developers who would just experiment with the API. Selecting a constant value, like 10
, was probably deemed too arbitrary, so they opted for the Environment.ProcessorCount
. It makes sense. It is logical to assume that there is some correlation between the power of the machine, and the power of the network that is connected to.
My suggestion for configuring the Parallel.ForEachAsync
/MaxDegreeOfParallelism
is to not rely on the default, experiment with various values, start with a small value like 2
, be conservative, and consider making it configurable manually through the App.config. The optimal value might change during the lifetime of the application.
It should be noted that the .NET 6 Parallel.ForEachAsync
method has not identical behavior with the one-liner ForEachAsync
that is shown in the question. The most important difference is that in case of an exception the Parallel.ForEachAsync
will stop invoking the body
, and will complete ASAP as faulted. On the contrary the one-liner will continue invoking the body
, as long as there is a worker task still alive. Each error will kill one of the dop
workers. If you are unlucky to have exactly dop - 1
early exceptions, the last standing worker will slowly process the remaining elements alone, until the exceptions are finally surfaced. For implementations with better behavior on .NET platforms older than .NET 6, look at this question, or this.