Parallel.ForEach
parallel programming
C#
concurrency
task management

How can I limit Parallel.ForEach?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Parallel.ForEach in .NET serves as a powerful tool for concurrent data processing, enabling significant improvements in performance by utilizing multiple threads. However, without appropriate management, it may lead to resource exhaustion, reduced performance, or unintended side effects. Limiting the degree of parallelism allows a developer to maintain better control over system resources, ensuring effective execution while maximizing throughput.

Understanding Parallel.ForEach

Parallel.ForEach is part of the Parallel class in the Task Parallel Library (TPL). It operates similarly to a conventional foreach loop, but it distributes iterations across multiple threads. This distribution can lead to more efficient use of multiple CPU cores and improved execution speed when processing large data sets or computationally intensive tasks.

Why Limit Parallelism?

While parallelization can enhance performance, an unbounded number of threads may consume excessive CPU time, increase context switching, and lead to resource contention or deadlock. Controlling the degree of parallelism helps in:

  • Resource Management: Keeps CPU and memory usage in check.
  • Performance Tuning: Prevents bottlenecks due to excessive context switching.
  • Scalability: Enables predictable performance as data size or complexity grows.
  • Stability: Avoids running into thread starvation or deadlock situations.

Techniques to Limit Parallel.ForEach

1. Using ParallelOptions

The ParallelOptions class allows you to specify the maximum degree of parallelism.

csharp
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5class Example
6{
7    static void Main()
8    {
9        var data = new List<int> { 1, 2, 3, 4, 5 };
10        var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
11
12        Parallel.ForEach(data, options, item =>
13        {
14            Console.WriteLine($"Processing item {item}");
15        });
16    }
17}

In this example, at most two tasks are processed concurrently due to MaxDegreeOfParallelism = 2.

2. Creating Custom Partitioners

Custom partitioners can provide fine-grained control over how data is partitioned and processed.

csharp
1using System;
2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Linq;
5using System.Threading.Tasks;
6
7class Example
8{
9    static void Main()
10    {
11        var data = Enumerable.Range(1, 100).ToList();
12        var partitioner = Partitioner.Create(0, data.Count, 10); // Use a range size of 10
13
14        Parallel.ForEach(partitioner, (range, state) =>
15        {
16            for (int i = range.Item1; i < range.Item2; i++)
17            {
18                Console.WriteLine($"Processing item {data[i]}");
19            }
20        });
21    }
22}

3. Combining with Cancellation Tokens

Limiting parallelism can be more dynamic when combined with cancellation tokens, allowing you to stop processing based on external conditions.

csharp
1using System;
2using System.Collections.Generic;
3using System.Threading;
4using System.Threading.Tasks;
5
6class Example
7{
8    static void Main()
9    {
10        var data = new List<int> { 1, 2, 3, 4, 5 };
11        var cts = new CancellationTokenSource();
12        var options = new ParallelOptions
13        {
14            MaxDegreeOfParallelism = 2,
15            CancellationToken = cts.Token
16        };
17
18        Task.Run(() =>
19        {
20            // Simulate cancellation
21            Thread.Sleep(100);
22            cts.Cancel();
23        });
24
25        try
26        {
27            Parallel.ForEach(data, options, item =>
28            {
29                Console.WriteLine($"Processing item {item}");
30            });
31        }
32        catch (OperationCanceledException)
33        {
34            Console.WriteLine("Operation was canceled.");
35        }
36    }
37}

Summary Table

TechniqueDescriptionExample Code Available
ParallelOptionsDirectly set MaxDegreeOfParallelism using options.Yes
Custom PartitionerCreate custom partitions of data for fine-grained control.Yes
With Cancellation TokenCombine with token to support dynamic cancellation.Yes

Additional Considerations

When limiting parallelism, consider:

  • System Configuration: Hardware and existing workloads on the system.
  • Task Complexity: The computational cost of your tasks may dictate suitable parallelism levels.
  • Testing: Always test under expected production conditions since behavior can widely vary with data size and system load.

Conclusion

Limiting parallelism in Parallel.ForEach can enhance your application's ability to use system resources effectively and maintain stability. With options like ParallelOptions, custom partitioners, and cancellation tokens, you possess the tools necessary to develop responsive, high-performance applications. By understanding and implementing these techniques, you can fine-tune your concurrent processing tasks to achieve the desired balance between performance and resource utilization.


Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.