Procedimiento Escribir un bucle Parallel.ForEach sencillo

En este artículo se describe cómo usar un bucle Parallel.ForEach para habilitar el paralelismo de datos sobre cualquier origen de datos System.Collections.IEnumerable o System.Collections.Generic.IEnumerable<T>.

Nota

En esta documentación, se utilizan expresiones lambda para definir delegados en PLINQ. Si no está familiarizado con las expresiones lambda de C# o Visual Basic, vea Expresiones lambda en PLINQ y TPL.

Ejemplo

En este ejemplo se muestra el uso de Parallel.ForEach en operaciones intensivas de CPU. Al ejecutar el ejemplo, genera aleatoriamente 2 millones de números e intenta filtrar por números primos. El primer caso recorre en iteración la colección a través de un bucle for. El segundo caso recorre en iteración la colección a través de Parallel.ForEach. El tiempo resultante que tarda cada iteración se muestra cuando finaliza la aplicación.

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 any key 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 any key 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

Un bucle Parallel.ForEach funciona como un bucle Parallel.For. El bucle divide la colección de origen y programa el trabajo en varios subprocesos en función del entorno del sistema. Mientras más procesadores haya en el sistema, más rápido se ejecutará el método paralelo. Puede que en algunas colecciones de origen un bucle secuencial sea más rápido, dependiendo del tamaño del origen y del tipo de trabajo que realiza el bucle. Para más información sobre el rendimiento, vea Problemas potenciales en el paralelismo de datos y tareas.

Para obtener más información sobre los bucles paralelos, vea Procedimiento para escribir un bucle Parallel.For sencillo.

Para usar el bucle Parallel.ForEach con una colección no genérica, puede usar el método de extensión Enumerable.Cast para convertir la colección en una colección genérica, como se muestra en el siguiente ejemplo:

Parallel.ForEach(nonGenericCollection.Cast<object>(),
    currentElement =>
    {
    });
Parallel.ForEach(nonGenericCollection.Cast(Of Object), _
                 Sub(currentElement)
                     ' ... work with currentElement
                 End Sub)

También puede utilizar Parallel LINQ (PLINQ) para paralelizar el procesamiento de orígenes de datos IEnumerable<T>. PLINQ permite usar la sintaxis de consulta declarativa para expresar el comportamiento del bucle. Para más información, consulte Parallel LINQ (PLINQ).

Compilación y ejecución del código

Puede compilar el código como una aplicación de consola de .NET Framework o como una aplicación de consola de .NET Core.

En Visual Studio, hay plantillas de aplicación de consola de Visual Basic y C# para Windows Desktop y .NET Core.

Desde la línea de comandos, puede usar la CLI de .NET y sus comandos (por ejemplo, dotnet new console o dotnet new console -lang vb) o crear el archivo y usar el compilador de línea de comandos de una aplicación .NET Framework.

Para ejecutar una aplicación de consola .NET Core desde la línea de comandos, use dotnet run desde la carpeta que contenga la aplicación.

Para ejecutar la aplicación de consola desde Visual Studio, presione F5.

Vea también