Share via


방법: 간단한 Parallel.ForEach 루프 작성

이 문서에서는 Parallel.ForEach 루프를 사용하여 System.Collections.IEnumerable 또는 System.Collections.Generic.IEnumerable<T> 데이터 원본에 대한 데이터 병렬 처리를 사용하도록 설정하는 방법을 보여 줍니다.

참고 항목

이 문서에서는 람다 식을 사용하여 PLINQ에 대리자를 정의합니다. C# 또는 Visual Basic의 람다 식을 잘 모르는 경우 PLINQ 및 TPL의 람다 식을 참조하세요.

예시

이 예에서는 CPU 집약적인 작업에 대한 Parallel.ForEach를 보여 줍니다. 예를 실행하면 200만 개의 숫자가 임의로 생성되고 소수로 필터링을 시도합니다. 첫 번째 사례는 for 루프를 통해 컬렉션을 반복합니다. 두 번째 사례는 Parallel.ForEach를 통해 컬렉션을 반복합니다. 애플리케이션이 완료되면 각 반복에 소요된 결과 시간이 표시됩니다.

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

Parallel.ForEach 루프는 Parallel.For 루프처럼 작동합니다. 해당 루프는 원본 컬렉션을 분할하고 시스템 환경에 따라 여러 스레드에서 작업을 예약합니다. 시스템에 프로세서가 많을수록 병렬 메서드가 더 빠르게 실행됩니다. 일부 원본 컬렉션의 경우 원본의 크기 및 수행되는 루프 작업의 종류에 따라 순차 루프가 더 빠를 수 있습니다. 성능에 대한 자세한 내용은 데이터 및 작업 병렬 처리에서 발생할 수 있는 문제를 참조하세요.

병렬 루프에 대한 자세한 내용은 방법: 간단한 Parallel.For 루프 작성을 참조하세요.

제네릭이 아닌 컬렉션이 있는 Parallel.ForEach 루프를 사용하려면 다음 예와 같이 Enumerable.Cast 확장 메서드를 사용하여 컬렉션을 일반 컬렉션으로 변환할 수 있습니다.

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

병렬 LINQ(PLINQ)를 사용하여 IEnumerable<T> 데이터 소스의 처리를 병렬 처리할 수도 있습니다. PLINQ를 통해 선언 쿼리 구문을 사용하여 루프 동작을 표현할 수 있습니다. 자세한 내용은 PLINQ(병렬 LINQ)를 참조하세요.

코드 컴파일 및 실행

.NET Framework의 콘솔 애플리케이션 또는 .NET Core의 콘솔 애플리케이션으로 코드를 컴파일할 수 있습니다.

Visual Studio에는 Windows Desktop 및 .NET Core용 Visual Basic 및 C# 콘솔 애플리케이션 템플릿이 있습니다.

명령줄에서 .NET CLI 명령(예: dotnet new console 또는 dotnet new console -lang vb)을 사용하거나 파일을 만들고 .NET Framework 애플리케이션용 명령줄 컴파일러를 사용할 수 있습니다.

명령줄의 .NET Core 콘솔 애플리케이션을 실행하려면 애플리케이션이 포함된 폴더에서 dotnet run을 사용하세요.

Visual Studio에서 콘솔 애플리케이션을 실행하려면 F5 키를 누르세요.

참고 항목