Vorgehensweise: Schreiben einer einfachen Parallel.For-Schleife

Dieses Thema enthält zwei Beispiele, die die Parallel.For-Methode veranschaulichen. Im ersten Beispiel wird die Parallel.For(Int64, Int64, Action<Int64>)-Methodenüberladung und im zweiten Beispiel wird die Parallel.For(Int32, Int32, Action<Int32>)-Überladung verwendet. Dies beiden Überladungen sind die einfachsten Überladungen der Parallel.For-Methode. Sie können diese beiden Überladungen der Parallel.For-Methode verwenden, wenn keiner der folgenden Vorgänge erforderlich ist: Abbrechen der Schleife, vorzeitiges Beenden der Schleifeniterationen oder Beibehalten eines threadlokalen Zustands.

Hinweis

Diese Dokumentation definiert Delegaten in TPL mithilfe von Lambdaausdrücken. Falls Sie mit der Verwendung von Lambda-Ausdrücken in C# oder Visual Basic nicht vertraut sind, finden Sie entsprechende Informationen unter Lambda Expressions in PLINQ and TPL (Lambda-Ausdrücke in PLINQ und TPL).

Im ersten Beispiel wird die Größe der Dateien in einem einzelnen Verzeichnis berechnet. Im zweiten Beispiel wird das Produkt von zwei Matrizen berechnet.

Größenbeispiel für ein Verzeichnis

Dieses Beispiel ist ein einfaches Befehlszeilenprogramm, das die Gesamtgröße der Dateien in einem Verzeichnis berechnet. Das Programm erwartet einen einzelnen Verzeichnispfad als Argument und meldet die Anzahl sowie die Gesamtgröße der Dateien in diesem Verzeichnis. Nachdem das Programm überprüft hat, ob das Verzeichnis vorhanden ist, verwendet es die Parallel.For-Methode, um die Dateien im Verzeichnis aufzuzählen und deren Größen zu bestimmen. Jede Dateigröße wird dann der totalSize-Variablen hinzugefügt. Beachten Sie, dass die Addition durch Aufrufen derInterlocked.Add-Methode ausgeführt wird, sodass die Addition als atomarer Vorgang ausgeführt wird. Andernfalls könnten mehrere Aufgaben versuchen, die totalSize-Variable gleichzeitig zu aktualisieren.

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

public class Example
{
   public static void Main(string[] args)
   {
      long totalSize = 0;
      
      if (args.Length == 0) {
         Console.WriteLine("There are no command line arguments.");
         return;
      }
      if (! Directory.Exists(args[0])) {
         Console.WriteLine("The directory does not exist.");
         return;
      }

      String[] files = Directory.GetFiles(args[0]);
      Parallel.For(0, files.Length,
                   index => { FileInfo fi = new FileInfo(files[index]);
                              long size = fi.Length;
                              Interlocked.Add(ref totalSize, size);
                   } );
      Console.WriteLine("Directory '{0}':", args[0]);
      Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize);
   }
}
// The example displaysoutput like the following:
//       Directory 'c:\windows\':
//       32 files, 6,587,222 bytes
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim totalSize As Long = 0

        Dim args() As String = Environment.GetCommandLineArgs()
        If args.Length = 1 Then
            Console.WriteLine("There are no command line arguments.")
            Return
        End If
        If Not Directory.Exists(args(1))
            Console.WriteLine("The directory does not exist.")
            Return
        End If

        Dim files() As String = Directory.GetFiles(args(1))
        Parallel.For(0, files.Length,
                     Sub(index As Integer)
                         Dim fi As New FileInfo(files(index))
                         Dim size As Long = fi.Length
                         Interlocked.Add(totalSize, size)
                     End Sub)
        Console.WriteLine("Directory '{0}':", args(1))
        Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize)
    End Sub
End Module
' The example displays output like the following:
'       Directory 'c:\windows\':
'       32 files, 6,587,222 bytes

Matrix- und Stopwatch-Beispiel

In diesem Beispiel wird die Parallel.For-Methode verwendet, um das Produkt von zwei Matrizen zu berechnen. In diesem Beispiel wird außerdem gezeigt, wie die System.Diagnostics.Stopwatch-Klasse verwendet werden kann, um die Leistung einer parallelen Schleife mit der einer nicht parallelen Schleife zu vergleichen. Weil in dem Beispiel sehr viel Ausgabe generiert werden kann, wird in ihm ermöglicht, Ausgabe in eine Datei umzuleiten.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

class MultiplyMatrices
{
    #region Sequential_Loop
    static void MultiplyMatricesSequential(double[,] matA, double[,] matB,
                                            double[,] result)
    {
        int matACols = matA.GetLength(1);
        int matBCols = matB.GetLength(1);
        int matARows = matA.GetLength(0);

        for (int i = 0; i < matARows; i++)
        {
            for (int j = 0; j < matBCols; j++)
            {
                double temp = 0;
                for (int k = 0; k < matACols; k++)
                {
                    temp += matA[i, k] * matB[k, j];
                }
                result[i, j] += temp;
            }
        }
    }
    #endregion

    #region Parallel_Loop
    static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result)
    {
        int matACols = matA.GetLength(1);
        int matBCols = matB.GetLength(1);
        int matARows = matA.GetLength(0);

        // A basic matrix multiplication.
        // Parallelize the outer loop to partition the source array by rows.
        Parallel.For(0, matARows, i =>
        {
            for (int j = 0; j < matBCols; j++)
            {
                double temp = 0;
                for (int k = 0; k < matACols; k++)
                {
                    temp += matA[i, k] * matB[k, j];
                }
                result[i, j] = temp;
            }
        }); // Parallel.For
    }
    #endregion

    #region Main
    static void Main(string[] args)
    {
        // Set up matrices. Use small values to better view
        // result matrix. Increase the counts to see greater
        // speedup in the parallel loop vs. the sequential loop.
        int colCount = 180;
        int rowCount = 2000;
        int colCount2 = 270;
        double[,] m1 = InitializeMatrix(rowCount, colCount);
        double[,] m2 = InitializeMatrix(colCount, colCount2);
        double[,] result = new double[rowCount, colCount2];

        // First do the sequential version.
        Console.Error.WriteLine("Executing sequential loop...");
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        MultiplyMatricesSequential(m1, m2, result);
        stopwatch.Stop();
        Console.Error.WriteLine("Sequential loop time in milliseconds: {0}",
                                stopwatch.ElapsedMilliseconds);

        // For the skeptics.
        OfferToPrint(rowCount, colCount2, result);

        // Reset timer and results matrix.
        stopwatch.Reset();
        result = new double[rowCount, colCount2];

        // Do the parallel loop.
        Console.Error.WriteLine("Executing parallel loop...");
        stopwatch.Start();
        MultiplyMatricesParallel(m1, m2, result);
        stopwatch.Stop();
        Console.Error.WriteLine("Parallel loop time in milliseconds: {0}",
                                stopwatch.ElapsedMilliseconds);
        OfferToPrint(rowCount, colCount2, result);

        // Keep the console window open in debug mode.
        Console.Error.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
    #endregion

    #region Helper_Methods
    static double[,] InitializeMatrix(int rows, int cols)
    {
        double[,] matrix = new double[rows, cols];

        Random r = new Random();
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                matrix[i, j] = r.Next(100);
            }
        }
        return matrix;
    }

    private static void OfferToPrint(int rowCount, int colCount, double[,] matrix)
    {
        Console.Error.Write("Computation complete. Print results (y/n)? ");
        char c = Console.ReadKey(true).KeyChar;
        Console.Error.WriteLine(c);
        if (Char.ToUpperInvariant(c) == 'Y')
        {
            if (!Console.IsOutputRedirected &&
                RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                Console.WindowWidth = 180;
            }

            Console.WriteLine();
            for (int x = 0; x < rowCount; x++)
            {
                Console.WriteLine("ROW {0}: ", x);
                for (int y = 0; y < colCount; y++)
                {
                    Console.Write("{0:#.##} ", matrix[x, y]);
                }
                Console.WriteLine();
            }
        }
    }
    #endregion
}
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Threading.Tasks

Module MultiplyMatrices
#Region "Sequential_Loop"
    Sub MultiplyMatricesSequential(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As Double(,))
        Dim matACols As Integer = matA.GetLength(1)
        Dim matBCols As Integer = matB.GetLength(1)
        Dim matARows As Integer = matA.GetLength(0)

        For i As Integer = 0 To matARows - 1
            For j As Integer = 0 To matBCols - 1
                Dim temp As Double = 0
                For k As Integer = 0 To matACols - 1
                    temp += matA(i, k) * matB(k, j)
                Next
                result(i, j) += temp
            Next
        Next
    End Sub
#End Region

#Region "Parallel_Loop"
    Private Sub MultiplyMatricesParallel(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As Double(,))
        Dim matACols As Integer = matA.GetLength(1)
        Dim matBCols As Integer = matB.GetLength(1)
        Dim matARows As Integer = matA.GetLength(0)

        ' A basic matrix multiplication.
        ' Parallelize the outer loop to partition the source array by rows.
        Parallel.For(0, matARows, Sub(i)
                                      For j As Integer = 0 To matBCols - 1
                                          Dim temp As Double = 0
                                          For k As Integer = 0 To matACols - 1
                                              temp += matA(i, k) * matB(k, j)
                                          Next
                                          result(i, j) += temp
                                      Next
                                  End Sub)
    End Sub
#End Region

#Region "Main"
    Sub Main(ByVal args As String())
        ' Set up matrices. Use small values to better view 
        ' result matrix. Increase the counts to see greater 
        ' speedup in the parallel loop vs. the sequential loop.
        Dim colCount As Integer = 180
        Dim rowCount As Integer = 2000
        Dim colCount2 As Integer = 270
        Dim m1 As Double(,) = InitializeMatrix(rowCount, colCount)
        Dim m2 As Double(,) = InitializeMatrix(colCount, colCount2)
        Dim result As Double(,) = New Double(rowCount - 1, colCount2 - 1) {}

        ' First do the sequential version.
        Console.Error.WriteLine("Executing sequential loop...")
        Dim stopwatch As New Stopwatch()
        stopwatch.Start()

        MultiplyMatricesSequential(m1, m2, result)
        stopwatch.[Stop]()
        Console.Error.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)

        ' For the skeptics.
        OfferToPrint(rowCount, colCount2, result)

        ' Reset timer and results matrix. 
        stopwatch.Reset()
        result = New Double(rowCount - 1, colCount2 - 1) {}

        ' Do the parallel loop.
        Console.Error.WriteLine("Executing parallel loop...")
        stopwatch.Start()
        MultiplyMatricesParallel(m1, m2, result)
        stopwatch.[Stop]()
        Console.Error.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
        OfferToPrint(rowCount, colCount2, result)

        ' Keep the console window open in debug mode.
        Console.Error.WriteLine("Press any key to exit.")
        Console.ReadKey()
    End Sub
#End Region

#Region "Helper_Methods"
    Function InitializeMatrix(ByVal rows As Integer, ByVal cols As Integer) As Double(,)
        Dim matrix As Double(,) = New Double(rows - 1, cols - 1) {}

        Dim r As New Random()
        For i As Integer = 0 To rows - 1
            For j As Integer = 0 To cols - 1
                matrix(i, j) = r.[Next](100)
            Next
        Next
        Return matrix
    End Function

    Sub OfferToPrint(ByVal rowCount As Integer, ByVal colCount As Integer, ByVal matrix As Double(,))
        Console.Error.Write("Computation complete. Display results (y/n)? ")
        Dim c As Char = Console.ReadKey(True).KeyChar
        Console.Error.WriteLine(c)
        If Char.ToUpperInvariant(c) = "Y"c Then
            If Not Console.IsOutputRedirected AndAlso
                RuntimeInformation.IsOSPlatform(OSPlatform.Windows) Then Console.WindowWidth = 168

            Console.WriteLine()
            For x As Integer = 0 To rowCount - 1
                Console.WriteLine("ROW {0}: ", x)
                For y As Integer = 0 To colCount - 1
                    Console.Write("{0:#.##} ", matrix(x, y))
                Next
                Console.WriteLine()
            Next
        End If
    End Sub
#End Region
End Module

Wenn Code, einschließlich Schleifen, parallelisiert wird, besteht ein wichtiges Ziel darin, die Prozessoren so viel wie möglich zu nutzen, ohne den Punkt für die Parallelisierung zu überschreiten, ab dem der Aufwand für die parallele Verarbeitung jegliche Leistungsvorteile negiert. In diesem speziellen Beispiel wird nur die äußere Schleife parallelisiert, weil in der inneren Schleife nicht sehr viel Arbeit ausgeführt wird. Die Kombination aus einer kleinen Menge von Arbeit und unerwünschten Cacheeffekten kann zu Leistungseinbußen in geschachtelten parallelen Schleifen führen. Daher ist eine Parallelisierung der äußeren Schleife die beste Möglichkeit, die Vorteile der Parallelität in den meisten Systemen zu maximieren.

Der Delegat

Der dritte Parameter dieser Überladung von For ist ein Delegat des Typs Action<int> in C# oder Action(Of Integer) in Visual Basic. Ein Action-Delegat gibt unabhängig davon, ob er keinen, einen oder sechzehn Typparameter hat, immer „void“ zurück. In Visual Basic wird das Verhalten eines Action-Delegaten mit einer Sub-Prozedur definiert. Im Beispiel wird ein Lambdaausdruck verwendet, um den Delegaten zu erstellen. Sie können den Delegaten jedoch auch auf andere Art und Weise erstellen. Weitere Informationen finden Sie unter Lambdaausdrücke in PLINQ und TPL.

Der Iterationswert

Der Delegat hat einen einzelnen Eingabeparameter, dessen Wert der aktuellen Iteration entspricht. Dieser Iterationswert wird von der Runtime bereitgestellt, und sein Startwert ist der Index des ersten Elements im Segment (Partition) der Quelle, die im aktuellen Thread verarbeitet wird.

Wenn Sie mehr Kontrolle über den Parallelitätsumfang benötigen, verwenden Sie eine der Überladungen, die einen System.Threading.Tasks.ParallelOptions-Eingabeparameter hat, wie z. B.:Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).

Rückgabewert und Ausnahmebehandlung

For gibt ein System.Threading.Tasks.ParallelLoopResult-Objekt zurück, wenn alle Threads abgeschlossen sind. Dieser Rückgabewert ist hilfreich, wenn Sie eine Schleifeniteration manuell stoppen oder abbrechen, weil im ParallelLoopResult-Objekt Informationen wie die letzte Iteration, die vollständig ausgeführt wurde, gespeichert sind. Tritt in einem der Threads mindestens eine Ausnahme auf, wird eine System.AggregateException ausgelöst.

Im Code dieses Beispiels wird der Rückgabewert von For nicht verwendet.

Analyse und Leistung

Mit dem Leistung-Assistenten können die CPU-Nutzung auf Ihrem Computer anzeigen. Zum Experimentieren erhöhen Sie die Anzahl von Spalten und Zeilen in den Matrizen. Je größer die Matrizen sind, desto größer ist der Leistungsunterschied zwischen der parallelen und sequenziellen Version der Berechnung. Wenn die Matrix klein ist, wird die sequenzielle Version wegen des Mehraufwands für das Einrichten der parallelen Schleife schneller ausgeführt.

Synchrone Aufrufe freigegebener Ressourcen, wie die Konsole oder das Dateisystem, verringern die Leistung einer parallelen Schleife erheblich. Wenn Sie die Leistung messen, sollten Sie möglichst versuchen, Aufrufe wie Console.WriteLine innerhalb der Schleife zu vermeiden.

Kompilieren des Codes

Kopieren Sie diesen Code, und fügen Sie ihn in ein Visual Studio-Projekt ein.

Siehe auch