Combining Parallel and Sequential Modes in PLINQ
In complex software architecture, efficiency often comes from knowing when to use full power and when to scale back. PLINQ allows you to create Hybrid Queries, where you can switch between parallel and sequential modes within a single statement. This is crucial because some operations (like filtering) are "embarrassingly parallel," while others (like ordering) are inherently sequential.
1. Strategic Mode Switching: AsParallel vs. AsSequential
The most effective way to optimize a query is to use AsParallel() for the heavy lifting and AsSequential() for the final organization. This prevents the overhead of thread synchronization from slowing down operations that don't benefit from multiple cores.
Example: Parallel Filtering with Sequential Sorting
In this scenario, we use multiple cores to find specific numbers within a massive range, but switch to a single thread to sort them, as sorting in parallel often requires expensive re-merging of data.
2. Fine-Tuning Sequence: AsOrdered and AsUnordered
Sometimes you want the speed of parallel execution but the correctness of the original order. PLINQ provides two operators to toggle this behavior:
AsOrdered(): Tells PLINQ to track the original index of every element. While it allows parallel processing, it forces the system to reassemble the results in their original order.AsUnordered(): Removes the ordering constraint. If a query was previously marked as ordered, you can use this to "release" the requirement for a specific segment of the query to boost performance.
The Performance Trade-off
| Method | Impact on Performance | When to Use |
AsOrdered | High (Synchronization overhead) | When the sequence of output MUST match the input. |
AsUnordered | Low (Maximum speed) | When only the values matter, not their position. |
3. Practical Best Practices for Hybrid Queries
To write high-performance PLINQ code, follow these design patterns:
- Filter Early, Sort Late: Apply your
.Where()clauses in parallel to reduce the data size as quickly as possible. Only switch to.AsSequential()or.AsOrdered()once the dataset is small. - Identify Side Effects: If you are using
.ForAll(), you are strictly in parallel mode. If you need to update a UI or a non-thread-safe collection, always switch back using.AsSequential()or use proper locking. - Avoid Over-Ordering: Only use
.AsOrdered()if it is a functional requirement. Tracking indices across multiple threads consumes extra memory and CPU cycles.