How can I limit Parallel.ForEach in C#?

The Parallel.ForEach loop in C# executes iterations across multiple threads for improved performance. However, sometimes you need to limit the degree of parallelism to control resource usage, avoid overwhelming external systems, or manage thread contention. This is accomplished using ParallelOptions.

Syntax

Following is the basic syntax for Parallel.ForEach

Parallel.ForEach(collection, item => {
    // process item
});

Following is the syntax for limiting parallelism using ParallelOptions

Parallel.ForEach(collection, 
    new ParallelOptions { MaxDegreeOfParallelism = maxThreads }, 
    item => {
        // process item
    });

Parameters

  • collection − The data source to iterate over

  • ParallelOptions − Configuration object for parallel execution

  • MaxDegreeOfParallelism − Maximum number of concurrent threads (default is processor count)

  • action − The delegate to execute for each item

Parallel.ForEach Thread Limitation Unlimited Parallelism Uses all available cores Thread 1 Thread 2 Thread 3 Thread 4 + more threads... Limited Parallelism MaxDegreeOfParallelism = 2 Thread 1 Thread 2 Thread 3 Thread 4 Only 2 threads active Controlled resource usage and better performance predictability

Comparison: ForEach vs Parallel.ForEach

Example

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

class Program {
    static void Main(string[] args) {
        List<string> alphabets = new List<string> {
            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
            "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
            "U", "V", "W", "X", "Y", "Z"
        };

        Console.WriteLine("Sequential foreach loop:");
        var stopWatch = Stopwatch.StartNew();
        foreach (string alphabet in alphabets) {
            Console.WriteLine("Letter: {0}, Thread Id: {1}", 
                alphabet, Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(10); // Simulate work
        }
        Console.WriteLine("Sequential execution time: {0} ms<br>", 
            stopWatch.ElapsedMilliseconds);

        Console.WriteLine("Parallel.ForEach (unlimited):");
        stopWatch = Stopwatch.StartNew();
        Parallel.ForEach(alphabets, alphabet => {
            Console.WriteLine("Letter: {0}, Thread Id: {1}", 
                alphabet, Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(10); // Simulate work
        });
        Console.WriteLine("Parallel execution time: {0} ms", 
            stopWatch.ElapsedMilliseconds);
    }
}

The output of the above code is −

Sequential foreach loop:
Letter: A, Thread Id: 1
Letter: B, Thread Id: 1
Letter: C, Thread Id: 1
Letter: D, Thread Id: 1
Letter: E, Thread Id: 1
Sequential execution time: 280 ms

Parallel.ForEach (unlimited):
Letter: A, Thread Id: 1
Letter: G, Thread Id: 4
Letter: N, Thread Id: 6
Letter: T, Thread Id: 8
Letter: B, Thread Id: 1
Letter: H, Thread Id: 4
Parallel execution time: 95 ms

Using MaxDegreeOfParallelism to Limit Threads

Example

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class Program {
    static void Main(string[] args) {
        List<string> alphabets = new List<string> {
            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
            "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
            "U", "V", "W", "X", "Y", "Z"
        };

        Console.WriteLine("Limited to 2 threads:");
        Parallel.ForEach(
            alphabets,
            new ParallelOptions { MaxDegreeOfParallelism = 2 },
            alphabet => {
                Console.WriteLine("Letter: {0}, Thread Id: {1}", 
                    alphabet, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(50); // Simulate work
            }
        );
    }
}

The output of the above code is −

Limited to 2 threads:
Letter: N, Thread Id: 4
Letter: A, Thread Id: 1
Letter: O, Thread Id: 4
Letter: B, Thread Id: 1
Letter: P, Thread Id: 4
Letter: C, Thread Id: 1
Letter: Q, Thread Id: 4
Letter: D, Thread Id: 1
Letter: R, Thread Id: 4
Letter: E, Thread Id: 1

Common Use Cases for Limiting Parallelism

Scenario Reason to Limit Recommended MaxDegreeOfParallelism
Database operations Avoid connection pool exhaustion 2-4 threads
Web API calls Respect rate limits 5-10 threads
File I/O operations Reduce disk thrashing 2-3 threads
Memory-intensive tasks Prevent out-of-memory errors Environment.ProcessorCount / 2

Setting MaxDegreeOfParallelism Dynamically

Example

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program {
    static void Main(string[] args) {
        List<int> numbers = new List<int>();
        for (int i = 1; i <= 100; i++) {
            numbers.Add(i);
        }

        // Limit to half the processor count
        int maxThreads = Environment.ProcessorCount / 2;
        if (maxThreads < 1) maxThreads = 1;

        Console.WriteLine("Processor Count: {0}", Environment.ProcessorCount);
        Console.WriteLine("Max Threads Used: {0}", maxThreads);

        Parallel.ForEach(
            numbers,
            new ParallelOptions { MaxDegreeOfParallelism = maxThreads },
            number => {
                Console.WriteLine("Processing {0} on thread {1}", 
                    number, System.Threading.Thread.CurrentThread.ManagedThreadId);
            }
        );
    }
}

The output of the above code is −

Processor Count: 8
Max Threads Used: 4
Processing 1 on thread 1
Processing 26 on thread 4
Processing 51 on thread 6
Processing 76 on thread 8
Processing 2 on thread 1
Processing 27 on thread 4

Conclusion

Limiting Parallel.ForEach using MaxDegreeOfParallelism in ParallelOptions helps control resource usage and prevents system overload. This is essential when working with external resources like databases, APIs, or when memory constraints are a concern.

Updated on: 2026-03-17T07:04:36+05:30

2K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements