How to Use Parallel.For and Parallel.ForEach in C

Parallel processing in C# allows developers to execute iterations of a loop concurrently across multiple CPU cores to improve application performance. Part of the Task Parallel Library (TPL) under the System.Threading.Tasks namespace, Parallel.For and Parallel.ForEach handle thread scheduling, load balancing, and synchronization automatically. This article explains how to implement both constructs, manage execution options, maintain thread safety, and determine when parallel loops are the right tool for your workload.


When to Use Parallel Loops

Use Parallel.For and Parallel.ForEach when:

  • The work inside the loop is CPU-bound (e.g., mathematical calculations, image processing, parsing).
  • Each iteration is independent of the others.
  • The volume of work per iteration is large enough to offset the overhead of thread management.

Do not use them for I/O-bound operations (such as database queries or network requests); use asynchronous patterns like Task.WhenAll instead.


Using Parallel.For

Parallel.For executes a loop where the iterations are defined by an integer range, similar to a standard for loop.

Basic Syntax

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        int totalItems = 10;

        // Iterates from 0 up to (but not including) totalItems
        Parallel.For(0, totalItems, i =>
        {
            Console.WriteLine($"Processing index {i} on Thread {Environment.CurrentManagedThreadId}");
        });
    }
}

In this example, iterations may execute out of order because the runtime distributes the indices across available processor threads.


Using Parallel.ForEach

Parallel.ForEach operates over an IEnumerable<T>, functioning like a parallel version of a standard foreach loop.

Basic Syntax

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

class Program
{
    static void Main()
    {
        List<string> files = new() { "file1.txt", "file2.txt", "file3.txt", "file4.txt" };

        Parallel.ForEach(files, file =>
        {
            ProcessFile(file);
        });
    }

    static void ProcessFile(string fileName)
    {
        Console.WriteLine($"Processing {fileName} on Thread {Environment.CurrentManagedThreadId}");
    }
}

Controlling Concurrency with ParallelOptions

You can control the maximum number of concurrent threads and pass cancellation tokens using ParallelOptions.

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        using var cts = new CancellationTokenSource();

        var options = new ParallelOptions
        {
            // Limit to a specific number of concurrent threads
            MaxDegreeOfParallelism = Environment.ProcessorCount / 2,
            CancellationToken = cts.Token
        };

        try
        {
            Parallel.For(0, 100, options, (i, state) =>
            {
                options.CancellationToken.ThrowIfCancellationRequested();
                // Perform CPU-intensive work here
            });
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Execution was cancelled.");
        }
    }
}

Breaking or Stopping a Parallel Loop

To exit early from a parallel loop, use the ParallelLoopState object passed to the loop delegate:

  • state.Break(): Ensures that all iterations with an index lower than the current index still run, but prevents future iterations beyond the current one.
  • state.Stop(): Ceases execution as soon as possible, regardless of iteration index.
Parallel.For(0, 1000, (i, state) =>
{
    if (i == 50)
    {
        state.Stop();
        return;
    }

    // Work performed here
});

Ensuring Thread Safety

Because iterations execute simultaneously, shared data must be protected against race conditions.

1. Atomic Operations

Use the Interlocked class for simple numeric aggregations.

int totalSum = 0;

Parallel.For(1, 101, i =>
{
    Interlocked.Add(ref totalSum, i);
});

2. Thread-Safe Collections

Avoid standard collections like List<T> or Dictionary<TKey, TValue>. Instead, use thread-safe alternatives from System.Collections.Concurrent, such as ConcurrentBag<T> or ConcurrentDictionary<TKey, TValue>.

using System.Collections.Concurrent;

var results = new ConcurrentBag<int>();

Parallel.ForEach(Enumerable.Range(1, 50), number =>
{
    results.Add(number * 2);
});

Summary Checklist

  1. Verify Iteration Independence: Ensure no iteration depends on the state or completion order of another.
  2. Handle Exceptions: Unhandled exceptions in parallel loops are aggregated and thrown as an AggregateException.
  3. Avoid Over-Parallelization: For loops with minimal processing per element, the scheduling overhead can make parallel execution slower than sequential execution. Benchmark using tools like BenchmarkDotNet when optimizing critical paths.