如何:撰寫簡單的 Parallel.ForEach 迴圈
本文示範如何使用 Parallel.ForEach 迴圈來透過 System.Collections.IEnumerable 或 System.Collections.Generic.IEnumerable<T> 資料來源啟用資料平行處理原則。
注意
本文件使用 Lambda 運算式來定義 PLINQ 中的委派。 如果您不熟悉 C# 或 Visual Basic 中的 Lambda 運算式,請參閱 PLINQ 和 TPL 中的 Lambda 運算式。
範例
此範例針對 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 '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
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 可讓您使用宣告式查詢語法來表示迴圈行為。 如需詳細資訊,請參閱 Parallel LINQ (PLINQ)。
編譯並執行程式碼
您可將程式碼編譯為 .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。