Guide pratique pour écrire une boucle Parallel.ForEach simple
Cet exemple montre comment utiliser une boucle Parallel.ForEach pour activer le parallélisme des données sur une source de données System.Collections.IEnumerable ou System.Collections.Generic.IEnumerable<T>.
Notes
Cette documentation utilise des expressions lambda pour définir les délégués en PLINQ. Si les expressions lambda en C# ou Visual Basic ne vous sont pas familières, consultez Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches.
Exemple
Cet exemple illustre Parallel.ForEach pour des opérations gourmandes en processeur. Lorsque vous exécutez l’exemple, il génère aléatoirement 2 millions de nombres et tente de filtrer les nombres premiers. Le premier cas itère sur la collection par le biais d’une boucle for
. Le deuxième cas itère sur la collection par le biais de Parallel.ForEach. Le temps nécessaire à chaque itération s’affiche lorsque l’application a terminé.
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;
}
}
}
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
Une boule Parallel.ForEach fonctionne comme une boucle Parallel.For. Les boucles partitionnent la collection source et planifient le travail sur plusieurs threads en fonction de l’environnement système. Plus il y a de processeurs sur le système, plus la méthode parallèle s’exécute rapidement. Pour certaines collections sources, une boucle séquentielle peut s’avérer plus rapide, selon la taille de la source et le type de travail exécuté par la boucle. Pour plus d’informations sur les performances, consultez Pièges potentiels dans le parallélisme des données et des tâches.
Pour plus d’informations sur les boucles parallèles, consultez Guide pratique pour écrire une boucle Parallel.For simple.
Pour utiliser la boucle Parallel.ForEach avec une collection non générique, vous pouvez utiliser la méthode d’extension Enumerable.Cast pour convertir la collection en collection générique, comme indiqué dans l’exemple suivant :
Parallel.ForEach(nonGenericCollection.Cast<object>(),
currentElement =>
{
});
Parallel.ForEach(nonGenericCollection.Cast(Of Object), _
Sub(currentElement)
' ... work with currentElement
End Sub)
Vous pouvez également utiliser PLINQ (Parallel LINQ) pour paralléliser le traitement des sources de données IEnumerable<T>. PLINQ vous permet d’utiliser la syntaxe de requête déclarative pour exprimer le comportement de la boucle. Pour plus d’informations, consultez PLINQ (Parallel LINQ).
Compiler et exécuter le code
Vous pouvez compiler le code en tant qu’application de console pour .NET Framework ou en tant qu’application de console pour .NET Core.
Dans Visual Studio, il existe des modèles d’application de console Visual Basic et C# pour Windows Desktop et .NET Core.
À partir de la ligne de commande, vous pouvez utiliser les commandes CLI .NET (par exemple, dotnet new console
ou dotnet new console -lang vb
) ou créer le fichier et utiliser le compilateur en ligne de commande d’une application .NET Framework.
Pour exécuter une application de console .NET Core depuis la ligne de commande, utilisez dotnet run
depuis le dossier qui contient votre application.
Pour exécuter votre application de console depuis Visual Studio, appuyez sur F5.