如何:编写简单的 Parallel.ForEach 循环How to: Write a simple Parallel.ForEach loop

此示例展示了如何使用 Parallel.ForEach 循环,对任何 System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T> 数据源启用数据并行。This example shows how to use a Parallel.ForEach loop to enable data parallelism over any System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> data source.

备注

本文档使用 lambda 表达式在 PLINQ 中定义委托。This documentation uses lambda expressions to define delegates in PLINQ. 如果不熟悉 C# 或 Visual Basic 中的 lambda 表达式,请参阅 PLINQ 和 TPL 中的 Lambda 表达式If you are not familiar with lambda expressions in C# or Visual Basic, see Lambda expressions in PLINQ and TPL.

示例Example

此示例假定 C:\Users\Public\Pictures\Sample Pictures 文件夹中有几个 .jpg 文件,并创建名为“Modified”的新子文件夹。This example assumes you have several .jpg files in a C:\Users\Public\Pictures\Sample Pictures folder and creates a new sub-folder named Modified. 运行该示例时,它会旋转示例图片中的每个 .jpg 图像并将其保存到“Modified”文件夹When you run the example, it rotates each .jpg image in Sample Pictures and saves it to Modified. 可以根据需要修改这两个路径。You can modify the two paths as necessary.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing;

public class Example
{
    public static void Main()
    {
        // A simple source for demonstration purposes. Modify this path as necessary.
        string[] files = Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg");
        string newDir = @"C:\Users\Public\Pictures\Sample Pictures\Modified";
        Directory.CreateDirectory(newDir);

        // Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)
        Parallel.ForEach(files, (currentFile) =>
                                {
                                    // The more computational work you do here, the greater
                                    // the speedup compared to a sequential foreach loop.
                                    string filename = Path.GetFileName(currentFile);
                                    var bitmap = new Bitmap(currentFile);

                                    bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
                                    bitmap.Save(Path.Combine(newDir, filename));

                                    // Peek behind the scenes to see how work is parallelized.
                                    // But be aware: Thread contention for the Console slows down parallel loops!!!

                                    Console.WriteLine($"Processing {filename} on thread {Thread.CurrentThread.ManagedThreadId}");
                                    //close lambda expression and method invocation
                                });

        // Keep the console window open in debug mode.
        Console.WriteLine("Processing complete. Press any key to exit.");
        Console.ReadKey();
    }
}
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Drawing

Module ForEachDemo

    Sub Main()
        ' A simple source for demonstration purposes. Modify this path as necessary.
        Dim files As String() = Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures", "*.jpg")
        Dim newDir As String = "C:\Users\Public\Pictures\Sample Pictures\Modified"
        Directory.CreateDirectory(newDir)

        Parallel.ForEach(files, Sub(currentFile)
                                    ' The more computational work you do here, the greater 
                                    ' the speedup compared to a sequential foreach loop.
                                    Dim filename As String = Path.GetFileName(currentFile)
                                    Dim bitmap As New Bitmap(currentFile)

                                    bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone)
                                    bitmap.Save(Path.Combine(newDir, filename))

                                    ' Peek behind the scenes to see how work is parallelized.
                                    ' But be aware: Thread contention for the Console slows down parallel loops!!!

                                    Console.WriteLine($"Processing {filename} on thread {Thread.CurrentThread.ManagedThreadId}")
                                    'close lambda expression and method invocation
                                End Sub)


        ' Keep the console window open in debug mode.
        Console.WriteLine("Processing complete. Press any key to exit.")
        Console.ReadKey()
    End Sub
End Module

Parallel.ForEach 循环的工作原理类似 Parallel.For 循环。A Parallel.ForEach loop works like a Parallel.For loop. 该循环对源集合进行分区,并根据系统环境在多个线程上安排工作。The loop partitions the source collection and schedules the work on multiple threads based on the system environment. 系统上的处理器越多,并行方法的运行速度就越快。The more processors on the system, the faster the parallel method runs. 对于一些源集合,有序循环可能会更快,具体视源大小以及该循环要执行的工作类型而定。For some source collections, a sequential loop may be faster, depending on the size of the source and the kind of work the loop performs. 有关性能的详细信息,请参阅数据和任务并行的潜在问题For more information about performance, see Potential pitfalls in data and task parallelism.

若要详细了解并行循环,请参阅如何:编写简单的 Parallel.For 循环For more information about parallel loops, see How to: Write a simple Parallel.For loop.

若要将 Parallel.ForEach 与非泛型集合结合使用,可以使用 Enumerable.Cast 扩展方法,将集合转换为泛型集合,如下面的示例所示:To use Parallel.ForEach with a non-generic collection, you can use the Enumerable.Cast extension method to convert the collection to a generic collection, as shown in the following example:

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

还可以使用并行 LINQ (PLINQ) 并行处理 IEnumerable<T> 数据源。You can also use Parallel LINQ (PLINQ) to parallelize processing of IEnumerable<T> data sources. 借助 PLINQ,可以使用声明性查询语法来表达循环行为。PLINQ enables you to use declarative query syntax to express the loop behavior. 有关详细信息,请参阅并行 LINQ (PLINQ)For more information, see Parallel LINQ (PLINQ).

编译并运行代码Compile and run the code

可以作为 .NET Framework 的控制台应用程序或 .NET Core 的控制台应用程序编译代码。You can compile the code as a console application for .NET Framework or as a console application for .NET Core.

Visual Studio 中有适用于 Windows 桌面和 .NET Core 的 Visual Basic 和 C# 控制台应用程序模板。In Visual Studio, there are Visual Basic and C# console application templates for Windows Desktop and .NET Core.

从命令行,可使用 .NET Core CLI 命令(例如 dotnet new consoledotnet new console -lang vb),或者可创建文件并使用 .NET Framework 应用程序提供的命令行编译器。From the command line, you can use either the .NET Core CLI commands (for example, dotnet new console or dotnet new console -lang vb), or you can create the file and use the command-line compiler for a .NET Framework application.

对于.NET Core 项目,必须引用 System.Drawing.Common NuGet 包。For a .NET Core project, you must reference the System.Drawing.Common NuGet package. 在 Visual Studio 中,使用 NuGet 包管理器安装该包。In Visual Studio, use the NuGet Package Manager to install the package. 或者,也可以在 *.csproj 或 *.vbproj 文件中添加对包的引用:Alternatively, you can add a reference to the package in your *.csproj or *.vbproj file:

<ItemGroup>
     <PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>

要从命令行运行 .NET Core 控制台应用程序,请使用包含该应用程序的文件夹中的 dotnet runTo run a .NET Core console application from the command line, use dotnet run from the folder that contains your application.

要从 Visual Studio 中运行控制台应用程序,请按 F5。To run your console application from Visual Studio, press F5.

请参阅See also