C# > Asynchronous Programming > Parallel Programming > Parallel.For and Parallel.ForEach
Parallel.For and Parallel.ForEach Demonstration
This example demonstrates the usage of Parallel.For
and Parallel.ForEach
in C# for parallel processing of data. These methods are part of the Task Parallel Library (TPL) and allow you to efficiently distribute work across multiple threads, speeding up computationally intensive tasks. The example includes both a numeric loop using Parallel.For
and an enumerable collection using Parallel.ForEach
.
Understanding Parallel.For and Parallel.ForEach
Parallel.For
and Parallel.ForEach
are methods that allow you to execute loops in parallel. Parallel.For
is used for numeric loops where the number of iterations is known in advance. Parallel.ForEach
is used to iterate over collections in parallel. Both methods automatically manage the partitioning of the work and the synchronization of the threads. They are very useful when you have independent operations that can be performed on each item or iteration without dependencies on other operations. It's important to avoid shared state and race conditions to prevent errors or unexpected behaviors.
Parallel.For Example
This code initializes an array of 100 integers and then uses Parallel.For
to iterate through the array. Inside the loop, each element is multiplied by 2. The Console.WriteLine
statement shows which thread is processing which element. It's important to note that the order of output will likely be different each time you run the program due to the parallel execution. The Task.CurrentId
provides a unique ID for the currently executing task (thread). This helps to visualize the distribution of work across threads. The commented-out Thread.Sleep(10)
line can be uncommented to simulate a more realistic workload and better demonstrate the benefits of parallel processing.
using System;
using System.Threading.Tasks;
public class ParallelForExample
{
public static void Main(string[] args)
{
int[] numbers = new int[100];
// Initialize the array
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = i + 1;
}
// Parallel.For example
Console.WriteLine("Parallel.For execution:");
Parallel.For(0, numbers.Length, i =>
{
// Simulate a long-running operation
// Thread.Sleep(10); // Uncomment to simulate a more realistic workload
Console.WriteLine($"Processing element {i} on thread {Task.CurrentId}");
numbers[i] = numbers[i] * 2; // Example operation
});
Console.WriteLine("\nResult of Parallel.For (first 10 elements):");
for (int i = 0; i < 10; i++)
{
Console.Write(numbers[i] + " ");
}
Console.WriteLine();
Console.WriteLine("Press any key to continue to Parallel.ForEach example...");
Console.ReadKey();
}
}
Parallel.ForEach Example
This code creates a list of strings and then uses Parallel.ForEach
to iterate through the list. Inside the loop, each string is converted to uppercase. The Console.WriteLine
statement shows which thread is processing which name. Again, the order of the output will vary due to parallel execution. The Task.CurrentId
helps to identify which thread is doing the work. The commented-out Thread.Sleep(10)
line can be uncommented to simulate a more realistic workload.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class ParallelForEachExample
{
public static void Main(string[] args)
{
List<string> names = new List<string>() { "Alice", "Bob", "Charlie", "David", "Eve" };
// Parallel.ForEach example
Console.WriteLine("\nParallel.ForEach execution:");
Parallel.ForEach(names, name =>
{
// Simulate a long-running operation
// Thread.Sleep(10); // Uncomment to simulate a more realistic workload
Console.WriteLine($"Processing name {name} on thread {Task.CurrentId}");
string processedName = name.ToUpper(); // Example operation
});
Console.WriteLine("Parallel.ForEach completed.");
}
}
Concepts Behind the Snippet
The core concept is parallelism, which involves breaking down a task into smaller subtasks that can be executed concurrently. The Task Parallel Library (TPL) provides the Parallel.For
and Parallel.ForEach
methods to simplify the process of parallelizing loops. These methods handle the partitioning of the data, the scheduling of the tasks onto threads, and the aggregation of the results. This allows developers to focus on the logic of the individual tasks rather than the complexities of thread management.
Real-Life Use Case Section
Imagine processing a large image where each pixel needs to be analyzed and modified. Using Parallel.For
or Parallel.ForEach
, you can divide the image into sections and process each section in parallel, significantly reducing the overall processing time. Another example is processing a large dataset where each record needs to be transformed or analyzed. These kinds of tasks are generally CPU-bound and can significantly benefit from the distribution of work across multiple cores, or when you have multiple cores in your CPU.
Best Practices
Parallel.For
or Parallel.ForEach
after all iterations have completed.Partitioner
class.
Interview Tip
When discussing Parallel.For
and Parallel.ForEach
in an interview, emphasize your understanding of the underlying principles of parallelism and the importance of avoiding shared state and race conditions. Be prepared to discuss scenarios where these methods are appropriate and scenarios where they might not be the best choice (e.g., when the loop body is very short or when there are strong dependencies between iterations).
When to Use Them
Use Parallel.For
and Parallel.ForEach
when you have a computationally intensive loop where each iteration can be performed independently of the others. These methods are particularly effective when processing large datasets or performing operations on each element of a collection. Avoid using them for short loops or when there are strong dependencies between iterations.
Memory Footprint
Parallel execution can increase memory usage due to the need to manage multiple threads and potentially duplicate data. Be mindful of the memory footprint of your parallel loops, especially when dealing with large datasets. Minimize the amount of data that needs to be copied or shared between threads.
Alternatives
Alternatives to Parallel.For
and Parallel.ForEach
include using Task.Run
to manually create and manage tasks, or using dataflow pipelines (TPL Dataflow
) for more complex parallel processing scenarios. LINQ's AsParallel()
extension method can also be used for parallelizing LINQ queries. Each of these options has different trade-offs in terms of complexity and flexibility.
Pros
Cons
FAQ
-
What happens if an exception is thrown inside a Parallel.For or Parallel.ForEach loop?
Exceptions thrown within the loop are aggregated and re-thrown byParallel.For
orParallel.ForEach
after all iterations have completed. You can catch this aggregate exception and handle the individual exceptions within it. -
How do I control the degree of parallelism in a Parallel.For or Parallel.ForEach loop?
You can use theParallelOptions
class to specify the maximum degree of parallelism. This allows you to limit the number of threads used by the loop. -
When should I *not* use Parallel.For or Parallel.ForEach?
Avoid using them for short loops or when there are strong dependencies between iterations, or when the overhead of parallelization outweighs the performance benefits.