This article shows how to use a System.Threading.Tasks.Parallel.ForEach loop to enable data parallelism over any System.Collections.IEnumerable or System.Collections.Generic.IEnumerable data source.
Example
This example demonstrates System.Threading.Tasks.Parallel.ForEach for CPU-intensive operations. When you run the example, it randomly generates 2 million numbers and tries to filter to prime numbers. The first case iterates over the collection via a for loop. The second case iterates over the collection via System.Threading.Tasks.Parallel.ForEach. The resulting time taken by each iteration is displayed when the application is finished.
sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_parallel/cs/simpleforeach.cs03
// <snippet03>
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace ParallelExample
{
class Program
{
static void Main()
{
// 2 million
var limit = 2_000_000;
var numbers = Enumerable.Range(0, limit).ToList();
var watch = Stopwatch.StartNew();
var primeNumbersFromForeach = GetPrimeList(numbers);
watch.Stop();
var watchForParallel = Stopwatch.StartNew();
var primeNumbersFromParallelForeach = GetPrimeListWithParallel(numbers);
watchForParallel.Stop();
Console.WriteLine($"Classical foreach loop | Total prime numbers : {primeNumbersFromForeach.Count} | Time Taken : {watch.ElapsedMilliseconds} ms.");
Console.WriteLine($"Parallel.ForEach loop | Total prime numbers : {primeNumbersFromParallelForeach.Count} | Time Taken : {watchForParallel.ElapsedMilliseconds} ms.");
Console.WriteLine("Press 'Enter' to exit.");
Console.ReadLine();
}
/// <summary>
/// GetPrimeList returns Prime numbers by using sequential ForEach
/// </summary>
/// <param name="inputs"></param>
/// <returns></returns>
private static IList<int> GetPrimeList(IList<int> numbers) => numbers.Where(IsPrime).ToList();
/// <summary>
/// GetPrimeListWithParallel returns Prime numbers by using Parallel.ForEach
/// </summary>
/// <param name="numbers"></param>
/// <returns></returns>
private static IList<int> GetPrimeListWithParallel(IList<int> numbers)
{
var primeNumbers = new ConcurrentBag<int>();
Parallel.ForEach(numbers, number =>
{
if (IsPrime(number))
{
primeNumbers.Add(number);
}
});
return primeNumbers.ToList();
}
/// <summary>
/// IsPrime returns true if number is Prime, else false.(https://en.wikipedia.org/wiki/Prime_number)
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
private static bool IsPrime(int number)
{
if (number < 2)
{
return false;
}
for (var divisor = 2; divisor <= Math.Sqrt(number); divisor++)
{
if (number % divisor == 0)
{
return false;
}
}
return true;
}
}
}
// </snippet03>sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_parallel/vb/simpleforeach.vb03
'<snippet03>
Imports System.Collections.Concurrent
Namespace ParallelExample
Class Program
Shared Sub Main()
' 2 million
Dim limit = 2_000_000
Dim numbers = Enumerable.Range(0, limit).ToList()
Dim watch = Stopwatch.StartNew()
Dim primeNumbersFromForeach = GetPrimeList(numbers)
watch.Stop()
Dim watchForParallel = Stopwatch.StartNew()
Dim primeNumbersFromParallelForeach = GetPrimeListWithParallel(numbers)
watchForParallel.Stop()
Console.WriteLine($"Classical foreach loop | Total prime numbers : {primeNumbersFromForeach.Count} | Time Taken : {watch.ElapsedMilliseconds} ms.")
Console.WriteLine($"Parallel.ForEach loop | Total prime numbers : {primeNumbersFromParallelForeach.Count} | Time Taken : {watchForParallel.ElapsedMilliseconds} ms.")
Console.WriteLine("Press 'Enter' to exit.")
Console.ReadLine()
End Sub
' GetPrimeList returns Prime numbers by using sequential ForEach
Private Shared Function GetPrimeList(numbers As IList(Of Integer)) As IList(Of Integer)
Return numbers.Where(AddressOf IsPrime).ToList()
End Function
' GetPrimeListWithParallel returns Prime numbers by using Parallel.ForEach
Private Shared Function GetPrimeListWithParallel(numbers As IList(Of Integer)) As IList(Of Integer)
Dim primeNumbers = New ConcurrentBag(Of Integer)()
Parallel.ForEach(numbers, Sub(number)
If IsPrime(number) Then
primeNumbers.Add(number)
End If
End Sub)
Return primeNumbers.ToList()
End Function
' IsPrime returns true if number is Prime, else false.(https://en.wikipedia.org/wiki/Prime_number)
Private Shared Function IsPrime(number As Integer) As Boolean
If number < 2 Then
Return False
End If
For divisor = 2 To Math.Sqrt(number)
If number Mod divisor = 0 Then
Return False
End If
Next
Return True
End Function
End Class
End Namespace
'</snippet03>A System.Threading.Tasks.Parallel.ForEach loop works like a System.Threading.Tasks.Parallel.For loop. The loop partitions the source collection and schedules the work on multiple threads based on the system environment. The more processors on the system, the faster the parallel method runs. For some source collections, a sequential loop might be faster, depending on the size of the source and the kind of work the loop performs. For more information about performance, see Potential pitfalls in data and task parallelism.
For more information about parallel loops, see How to: Write a simple Parallel.For loop.
To use the System.Threading.Tasks.Parallel.ForEach loop with a non-generic collection, you can use the System.Linq.Enumerable.Cast extension method to convert the collection to a generic collection, as shown in the following example:
sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_parallel/cs/nongeneric.cs07
using System.Collections;
using System.Linq;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ArrayList nonGenericCollection = new ArrayList();
//<snippet07>
Parallel.ForEach(nonGenericCollection.Cast<object>(),
currentElement =>
{
});
//</snippet07>
}
}
}sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_parallel/vb/nongeneric.vb07
Imports System.Linq
Imports System.Collections
Imports System.Threading.Tasks
Module Module1
Sub Main()
Dim nonGenericCollection As New ArrayList()
'<snippet07>
Parallel.ForEach(nonGenericCollection.Cast(Of Object), _
Sub(currentElement)
' ... work with currentElement
End Sub)
'</snippet07>
End Sub
End ModuleYou can also use Parallel LINQ (PLINQ) to parallelize the processing of System.Collections.Generic.IEnumerable data sources. PLINQ enables you to use declarative query syntax to express the loop behavior. For more information, see Parallel LINQ (PLINQ).
Compile and run the code
You can compile the code as a console application for .NET Framework or as a console application for .NET Core.
In Visual Studio, there are Visual Basic and C# console application templates for Windows Desktop and .NET Core.
From the command line, you can use the .NET CLI commands (for example, dotnet new console or dotnet new console -lang vb) or create the file and use the command-line compiler for a .NET Framework application.
To run a .NET Core console application from the command line, use dotnet run from the folder that contains your application.
To run your console application from Visual Studio, press F5.