Instrukcje: pisanie prostej pętli Parallel.ForEach

W tym artykule pokazano, jak używać Parallel.ForEach pętli w celu umożliwienia równoległości danych w dowolnym System.Collections.IEnumerable źródle danych lub System.Collections.Generic.IEnumerable<T> w źródle danych.

Uwaga

Ta dokumentacja używa wyrażeń lambda do definiowania delegatów w PLINQ. Jeśli nie znasz wyrażeń lambda w języku C# lub Visual Basic, zobacz Wyrażenia lambda w plINQ i TPL.

Przykład

W tym przykładzie pokazano operacje intensywnie korzystające Parallel.ForEach z procesora CPU. Po uruchomieniu przykładu losowo generuje 2 miliony liczb i próbuje filtrować liczby pierwsze. Pierwszy przypadek iteruje kolekcję za pośrednictwem for pętli. Drugi przypadek iteruje kolekcję za pośrednictwem metody Parallel.ForEach. Wynikowy czas potrzebny na każdą iterację jest wyświetlany po zakończeniu działania aplikacji.

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

Pętla Parallel.ForEach działa jak pętla Parallel.For . Pętla partycjonuje kolekcję źródłową i planuje pracę na wielu wątkach w oparciu o środowisko systemowe. Tym więcej procesorów w systemie, tym szybciej działa metoda równoległa. W przypadku niektórych kolekcji źródłowych pętla sekwencyjna może być szybsza w zależności od rozmiaru źródła i rodzaju pracy wykonywanej przez pętlę. Aby uzyskać więcej informacji na temat wydajności, zobacz Potencjalne pułapki w danych i równoległości zadań.

Aby uzyskać więcej informacji na temat pętli równoległych, zobacz How to: Write a simple Parallel.For loop (Instrukcje: pisanie prostej pętli Parallel.For).

Aby użyć Parallel.ForEach pętli z kolekcją inną niż ogólna, możesz użyć Enumerable.Cast metody rozszerzenia, aby przekonwertować kolekcję na kolekcję ogólną, jak pokazano w poniższym przykładzie:

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

Można również użyć równoległego IEnumerable<T> LINQ (PLINQ), aby zrównoleglić przetwarzanie źródeł danych. PlINQ umożliwia używanie składni zapytań deklaratywnych do wyrażania zachowania pętli. Aby uzyskać więcej informacji, zobacz Parallel LINQ (PLINQ).

Kompilowanie i uruchamianie kodu

Kod można skompilować jako aplikację konsolową dla platformy .NET Framework lub jako aplikację konsolową dla platformy .NET Core.

W programie Visual Studio istnieją szablony aplikacji konsolowych Visual Basic i C# dla komputerów z systemem Windows i platformy .NET Core.

W wierszu polecenia można użyć poleceń interfejsu wiersza polecenia platformy .NET (na przykład dotnet new console lub dotnet new console -lang vb) lub utworzyć plik i użyć kompilatora wiersza polecenia dla aplikacji .NET Framework.

Aby uruchomić aplikację konsolową platformy .NET Core z poziomu wiersza polecenia, użyj folderu dotnet run zawierającego aplikację.

Aby uruchomić aplikację konsolową z poziomu programu Visual Studio, naciśnij klawisz F5.

Zobacz też