AsSequential
The difference between PLINQ and LINQ starts with the AsParallel clause. Converting from LINQ to PLINQ is often as simple as adding the AsParallel method to a LINQ query. Here is a basic LINQ query.
numbers.Select(/* selection */ ).OrderBy( /* sort */ );
Here is a parallel version of the same query, with the required AsParallel method added.
numbers.AsParallel().Select(/* selection */ ).OrderBy( /* sort */ );
So far, you’ve seen the AsParallel method prefixed to only the Select clause. However, Select is only one of the LINQ clauses that can take the AsParallel method as a prefix. Starting at that clause, the remainder of the query is conducted in parallel. Methods preceding the AsParallel clause in the query statement execute sequentially. In this example, the Select clause executes sequentially, but the GroupBy and OrderBy clauses execute in parallel.
numbers.Select(/* selection */ ).AsParallel().GroupBy( /* categorize */ )
.OrderBy( /* sort*/ );
AsSequential is the opposite of the AsParallel clause. AsSequential serializes portions of your LINQ query. You might choose this to resolve dependencies in a PLINQ query. You can then use AsSequential
to isolate the dependency and make a part of a PLINQ query sequential.
You might also decide that a portion of a PLINQ query is more
efficiently run in parallel as opposed to sequentially.
Use AsParallel and AsSequential
as gates for parallel and sequential execution, as shown in the
following diagram. Although it is not common, a single PLINQ query can
have multiple AsParallel and AsSequential clauses. Similar to the AsParallel clause, AsSequential
can be used to prefix a LINQ method. From that position of the query
forward, the remainder of the LINQ query executes sequentially—at least
until it encounters an AsParallel clause. The following diagram illustrates a PLINQ query with both AsParallel and AsSequential clauses. The Select and Groupby clauses execute in parallel, while the OrderBy clause is sequential.
For some, an orderly universe is important. Unfortunately, this is
contrary to the default behavior of PLINQ. The following code squares
and renders the values of an integer list.
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = from number in numbers.AsParallel() select number * number;
You might be surprised by the results, which are indeed squared but do not appear in the same order as the underlying list.
0 4 16 25 36 49 64 81 1 9
PLINQ creates tasks for the query. Each task is then scheduled and
placed on a queue in the .NET Framework 4 thread pool. The tasks are
then scheduled on processor cores and executed. But PLINQ does not
guarantee the order in which tasks will execute, so it is likely, if
not probable, that the list iteration is unordered.
If you prefer ordered results, use the AsOrdered
clause. The PLINQ query still executes in an unordered fashion to
improve performance and fully utilize the available processor cores.
However, the results are buffered and then reordered at the completion
of the query. This localizes the performance degradation to the AsOrdered clause.
Here is the modified query.
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = from number in numbers.AsParallel().AsOrdered()
select number * number;
The results are now ordered.
0 1 4 9 16 25 36 49 64 81
By default, PLINQ uses the available processor cores, up to a
maximum of 64. The goal, of course, is to keep the available processor
cores busy with active work, as shown in the following graph. The graph
depicts 100% utilization, which is ideal. This graph was taken during a
PLINQ query where the parallel clauses were compute bound.
You can explicitly set the maximum number of processor cores with the WithDegreeOfParallelism
clause. There are two primary reasons for using this clause. First,
this is useful when operations are I/O bound. I/O-bound threads are
sometimes suspended, which causes processor cores to be underutilized.
In this circumstance, you want to increase the degree of parallelism to
exceed the number of processor cores. Conversely, you can decrease the
number of tasks used in a PLINQ query with the WithDegreeOfParallelism clause. For example, you
could create a more cooperative environment for other running
applications by purposely reducing the degree of parallelism to less
than the number of available cores.
Assuming eight available processor cores, the following code reduces
the degree of parallelism. The amount of reduction depends on the
number of available cores.
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = numbers.AsParallel().WithDegreeOfParallelism(2).
Select(number=>PerformSelect(number)).ToList();