Delen via


Procedure: Een eenvoudige Parallel.ForEach-lus schrijven

In dit artikel wordt beschreven hoe u een Parallel.ForEach lus gebruikt om parallelle uitvoering van gegevens via een System.Collections.IEnumerable of System.Collections.Generic.IEnumerable<T> meer gegevensbronnen mogelijk te maken.

Notitie

In deze documentatie worden lambda-expressies gebruikt om gedelegeerden in PLINQ te definiëren. Als u niet bekend bent met lambda-expressies in C# of Visual Basic, raadpleegt u Lambda-expressies in PLINQ en TPL.

Opmerking

In dit voorbeeld ziet u hoe cpu-intensieve bewerkingen worden uitgevoerd Parallel.ForEach . Wanneer u het voorbeeld uitvoert, worden er willekeurig 2 miljoen getallen gegenereerd en wordt geprobeerd te filteren op priemgetallen. Het eerste geval doorloopt de verzameling via een for lus. In het tweede geval wordt de verzameling herhaald via Parallel.ForEach. De resulterende tijd die nodig is voor elke iteratie wordt weergegeven wanneer de toepassing is voltooid.

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

Een Parallel.ForEach lus werkt als een Parallel.For lus. De lus partitioneert de bronverzameling en plant het werk op meerdere threads op basis van de systeemomgeving. Hoe meer processors op het systeem, hoe sneller de parallelle methode wordt uitgevoerd. Voor sommige bronverzamelingen kan een sequentiële lus sneller zijn, afhankelijk van de grootte van de bron en het soort werk dat de lus uitvoert. Zie Potentiële valkuilen in gegevens- en taakparallellisme voor meer informatie over prestaties.

Zie How to: Write a simple Parallel.For loop voor meer informatie over parallelle lussen.

Als u de Parallel.ForEach lus met een niet-algemene verzameling wilt gebruiken, kunt u de Enumerable.Cast extensiemethode gebruiken om de verzameling te converteren naar een algemene verzameling, zoals wordt weergegeven in het volgende voorbeeld:

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

U kunt ook Parallel LINQ (PLINQ) gebruiken om de verwerking van IEnumerable<T> gegevensbronnen te parallelliseren. Met PLINQ kunt u de declaratieve querysyntaxis gebruiken om het lusgedrag uit te drukken. Zie Parallel LINQ (PLINQ) voor meer informatie.

De code compileren en uitvoeren

U kunt de code compileren als consoletoepassing voor .NET Framework of als consoletoepassing voor .NET Core.

In Visual Studio zijn er Visual Basic- en C#-consoletoepassingssjablonen voor Windows Desktop en .NET Core.

Vanaf de opdrachtregel kunt u de .NET CLI-opdrachten (bijvoorbeeld dotnet new console of dotnet new console -lang vb) gebruiken of het bestand maken en de opdrachtregelcompilator gebruiken voor een .NET Framework-toepassing.

Als u een .NET Core-consoletoepassing wilt uitvoeren vanaf de opdrachtregel, gebruikt dotnet run u deze vanuit de map die uw toepassing bevat.

Druk op F5 om uw consoletoepassing uit te voeren vanuit Visual Studio.

Zie ook