Master C# Programming From Scratch

Clear, interactive, and structured coding lessons designed for absolute beginners.

Parallel Processing

Parallel processing is a programming technique in which multiple operations are executed at the same time to improve performance and reduce the total execution time.

C# provides the Task Parallel Library (TPL), Parallel class, and other APIs that make it easier to execute independent operations concurrently.

Key Idea: Parallel processing is most useful when a large amount of independent work can be divided into smaller pieces and processed simultaneously.

What is Parallel Processing?

Parallel processing means dividing a large operation into smaller independent operations and allowing multiple operations to execute concurrently.

For example, suppose an application needs to process thousands of independent records. Instead of processing every record one after another, the work can be divided into smaller parts and processed concurrently.

Real-world Example: Image processing, data analysis, report generation, mathematical calculations, and batch processing can often benefit from parallel execution.

Sequential Processing vs Parallel Processing

In sequential processing, operations are performed one after another. In parallel processing, independent operations may execute concurrently.

Feature Sequential Processing Parallel Processing
Execution One operation after another Multiple independent operations concurrently
Performance May take more time for large workloads Can reduce execution time for suitable workloads
CPU Usage May use less CPU concurrency Can use multiple CPU cores
Best For Dependent operations Independent CPU-intensive operations

Parallel Class

The Parallel class is available in System.Threading.Tasks and provides simple methods for running iterations or actions concurrently.

Commonly used methods include:

  • Parallel.For()
  • Parallel.ForEach()
  • Parallel.Invoke()

Parallel.For()

Parallel.For() executes iterations of a loop concurrently whenever the runtime determines that parallel execution is beneficial.

C#
using System;

using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Parallel.For(1, 6, i =>
        {
            Console.WriteLine("Processing: " + i);
        });
    }
}

Possible Output

Output
Processing: 3
Processing: 1
Processing: 4
Processing: 2
Processing: 5
Important: The output order is not guaranteed because the iterations may execute concurrently.

How Parallel.For() Works

Instead of waiting for every iteration to finish before starting the next one, the runtime can divide the work among available worker threads.

Large Loop 1 to 1000
↓
Work Partitioning Divide iterations
↓
Worker 1
Worker 2
Worker 3
Worker 4
↓
Combined Result Processing completed

Parallel.ForEach()

Parallel.ForEach() is useful when working with a collection and each item can be processed independently.

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

class Program
{
    static void Main()
    {
        List<string> students = new List<string>
        {
            "Amit",
            "Priya",
            "Rahul",
            "Sneha",
            "Neha"
        };

        Parallel.ForEach(students, student =>
        {
            Console.WriteLine("Processing: " + student);
        });
    }
}

Possible Output

Output
Processing: Rahul
Processing: Amit
Processing: Sneha
Processing: Priya
Processing: Neha
Remember: The order of execution should not be relied upon when using parallel loops.

Parallel.Invoke()

Parallel.Invoke() executes multiple independent actions concurrently.

C#
using System;
 
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Parallel.Invoke(
            () => PrintMessage("Task A"),
            () => PrintMessage("Task B"),
            () => PrintMessage("Task C")
        );
    }

    static void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

Possible Output

Output
Task B
Task A
Task C

The three actions are independent, so their execution order can vary.

Controlling Parallelism

Sometimes an application should limit how many operations execute concurrently. This can be useful when the workload is large or when system resources need to be controlled.

ParallelOptions can be used to configure parallel execution.

C#
using System;
 
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        ParallelOptions options = new ParallelOptions
        {
            MaxDegreeOfParallelism = 2
        };

        Parallel.For(1, 7, options, i =>
        {
            Console.WriteLine("Processing item: " + i);
        });
    }
}

Explanation

In this example, the application allows a maximum of two parallel operations at a time.

Practical Tip: Limiting parallelism can help prevent excessive CPU usage and resource contention for certain workloads.

Parallel Processing and Shared Data

When multiple operations work concurrently, shared mutable data can become difficult to manage safely.

For example, multiple parallel operations should not blindly update the same normal collection without considering thread safety.

C#
using System;
 
using System.Collections.Concurrent;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        ConcurrentBag<int> numbers = new ConcurrentBag<int>();

        Parallel.For(1, 6, i =>
        {
            numbers.Add(i);
        });

        foreach (int number in numbers)
        {
            Console.WriteLine(number);
        }
    }
}

ConcurrentBag<T> is designed for scenarios where multiple threads may add or remove items concurrently.

Parallel.ForEach() with Filtering

Parallel processing can also be combined with conditions when only certain items need to be processed.

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

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int>
        {
            10, 15, 20, 25, 30
        };

        Parallel.ForEach(numbers, number =>
        {
            if (number % 10 == 0)
            {
                Console.WriteLine("Valid: " + number);
            }
        });
    }
}

Output

Output
Valid: 10
Valid: 20
Valid: 30

Example: Processing Student Scores

Consider an application that needs to process scores for many students. Each student's calculation is independent, so the calculations can be performed concurrently.

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

class Program
{
    static void Main()
    {
        List<int> scores = new List<int>
        {
            75, 82, 91, 68, 88
        };

        Parallel.ForEach(scores, score =>
        {
            string result = score >= 70 ? "Pass" : "Fail";

            Console.WriteLine(
                "Score: " + score + " - " + result
            );
        });
    }
}

Possible Output

Output
Score: 91 - Pass
Score: 75 - Pass
Score: 68 - Fail
Score: 88 - Pass
Score: 82 - Pass

Parallel Processing vs Multithreading

Feature Multithreading Parallel Processing
Main Focus Managing multiple threads Executing independent work concurrently
Abstraction Lower-level Higher-level
Common API Thread Parallel, TPL
Best Use Explicit thread control CPU-bound independent workloads
Complexity More manual management Usually simpler for parallel loops

Parallel Processing vs Asynchronous Programming

Parallel processing and asynchronous programming solve different problems, although they can sometimes be used together.

Feature Asynchronous Programming Parallel Processing
Primary Goal Avoid blocking while waiting Perform independent work concurrently
Common Scenario I/O-bound operations CPU-bound operations
Common Keywords/API async, await, Task Parallel.For, Parallel.ForEach
Example API or database call Large calculation or data processing

When Should You Use Parallel Processing?

  • When operations are independent.
  • When the workload is large enough to benefit from concurrency.
  • When the work is CPU-intensive.
  • When multiple CPU cores can be used effectively.
  • When the overhead of parallel execution is justified.
Important: Parallel processing does not automatically make every program faster. For very small workloads, the overhead of parallel execution can make the program slower.

When Should You Avoid Parallel Processing?

  • When operations depend heavily on each other.
  • When the workload is extremely small.
  • When shared mutable data is difficult to protect.
  • When ordering is critical.
  • When parallel execution creates unnecessary resource contention.

Exception Handling in Parallel Operations

Parallel operations can encounter exceptions. These exceptions need to be handled appropriately by the application.

C#
using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        try
        {
            Parallel.For(1, 6, i =>
            {
                if (i == 3)
                {
                    throw new InvalidOperationException(
                        "Invalid operation."
                    );
                }

                Console.WriteLine("Processing: " + i);
            });
        }
        catch (AggregateException ex)
        {
            foreach (Exception error in ex.InnerExceptions)
            {
                Console.WriteLine(error.Message);
            }
        }
    }
}

Parallel operations may report failures through AggregateException, which can contain one or more exceptions produced during the parallel operation.

Applications 🌍🤓

  • Large-scale data processing.
  • Image and video processing.
  • Report generation.
  • Scientific calculations.
  • Financial calculations.
  • Batch processing.
  • Data transformation.
  • Machine learning preprocessing.
CIIT Practical Point: In real .NET applications, parallel processing can be useful for CPU-intensive background work, batch data processing, report generation, and other independent calculations. Always measure performance before and after introducing parallelism.

Common Mistakes

  1. Assuming that parallel execution always makes code faster.
  2. Assuming that parallel loop iterations execute in order.
  3. Updating shared data without considering thread safety.
  4. Creating too much parallel work for a small operation.
  5. Ignoring CPU and memory consumption.
  6. Using parallel processing for operations that depend on previous results.

Best Practices

  • Use parallel processing for independent workloads.
  • Keep parallel operations reasonably small and focused.
  • Avoid unnecessary shared mutable state.
  • Use thread-safe collections when required.
  • Do not depend on execution order.
  • Control the degree of parallelism when necessary.
  • Measure performance instead of assuming improvement.

Interview Questions

1. What is parallel processing?

Parallel processing is a technique where independent operations are executed concurrently to improve performance for suitable workloads.

2. What is the Parallel class in C#?

The Parallel class provides methods such as Parallel.For(), Parallel.ForEach(), and Parallel.Invoke() for parallel execution.

3. What is Parallel.For()?

Parallel.For() executes iterations of a loop concurrently when parallel execution is appropriate.

4. What is Parallel.ForEach()?

Parallel.ForEach() processes elements of a collection concurrently when the operations are independent.

5. What is Parallel.Invoke()?

Parallel.Invoke() executes multiple independent actions concurrently.

6. Does Parallel.For() guarantee execution order?

No. The execution order of parallel iterations should not be relied upon.

7. When should parallel processing be used?

It should be considered when independent, sufficiently large, and CPU-intensive operations can benefit from concurrent execution.

8. What is MaxDegreeOfParallelism?

MaxDegreeOfParallelism limits the maximum number of concurrent operations used by certain parallel APIs.

Practice Programs

  1. Use Parallel.For() to print numbers from 1 to 20.
  2. Use Parallel.ForEach() to process a list of student names.
  3. Use Parallel.Invoke() to execute three independent methods.
  4. Create a program that processes 100 numbers in parallel.
  5. Use ParallelOptions to limit parallelism to two operations.
  6. Use ConcurrentBag<T> with a parallel loop.
  7. Compare sequential and parallel processing for a large calculation.

Summary

Parallel processing allows independent operations to execute concurrently and can improve performance for suitable workloads.

You learned important C# parallel programming APIs such as Parallel.For(), Parallel.ForEach(), Parallel.Invoke(), and ParallelOptions .

You also learned about parallel execution order, shared data, thread-safe collections, exception handling, and situations where parallel processing should or should not be used.

Parallel processing should be applied carefully and performance should be measured rather than assumed.