Gewusst wie: Beenden oder Verlassen einer Parallel.For-Schleife

Im folgenden Beispiel wird das Verlassen (oder Beenden in Visual Basic) einer For-Schleife sowie das Anhalten einer Schleife dargestellt. In diesem Zusammenhang bezeichnet "Verlassen" den Abschluss aller Iterationen in allen Threads die vor der aktuellen Iteration im aktuellen Thread erfolgen, und das anschließende Beenden der Schleife. " "Beenden" bedeutet, dass alle Iterationen sobald wie möglich gestoppt werden.

Beispiel

In diesem Beispiel wird eine For-Schleife verwendet. Auf dieselbe Weise können Sie aber auch eine ForEach-Schleife beenden oder abbrechen. In einer ForEach-Schleife wird für jedes Element in jeder Partition intern ein Iterationsindex generiert.

' How to: Stop or Break from a Parallel.For Loop
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks

Module ParallelForStop
    Sub Main()
        StopLoop()
        BreakAtThreshold()

        Console.WriteLine("Press any key to exit.")
        Console.ReadKey()
    End Sub

    Sub StopLoop()
        Console.WriteLine("Stop loop...")
        Dim source As Double() = MakeDemoSource(1000, 1)
        Dim results As New ConcurrentStack(Of Double)()

        ' i is the iteration variable. loopState is a 
        ' compiler-generated ParallelLoopState
        Parallel.For(0, source.Length, Sub(i, loopState)
                                           ' Take the first 100 values that are retrieved
                                           ' from anywhere in the source.
                                           If i < 100 Then
                                               ' Accessing shared object on each iteration
                                               ' is not efficient. See remarks.
                                               Dim d As Double = Compute(source(i))
                                               results.Push(d)
                                           Else
                                               loopState.[Stop]()
                                               Exit Sub

                                           End If
                                           ' Close lambda expression.
                                       End Sub)
        ' Close Parallel.For
        Console.WriteLine("Results contains {0} elements", results.Count())
    End Sub


    Sub BreakAtThreshold()
        Dim source As Double() = MakeDemoSource(10000, 1.0002)
        Dim results As New ConcurrentStack(Of Double)()

        ' Store all values below a specified threshold.
        Parallel.For(0, source.Length, Function(i, loopState)
                                           Dim d As Double = Compute(source(i))
                                           results.Push(d)
                                           If d > 0.2 Then
                                               ' Might be called more than once!
                                               loopState.Break()
                                               Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d)
                                               Thread.Sleep(1000)
                                           End If
                                           Return d
                                       End Function)

        Console.WriteLine("results contains {0} elements", results.Count())
    End Sub

    Function Compute(ByVal d As Double) As Double
        'Make the processor work just a little bit.
        Return Math.Sqrt(d)
    End Function


    ' Create a contrived array of monotonically increasing
    ' values for demonstration purposes. 
    Function MakeDemoSource(ByVal size As Integer, ByVal valToFind As Double) As Double()
        Dim result As Double() = New Double(size - 1) {}
        Dim initialval As Double = 0.01
        For i As Integer = 0 To size - 1
            initialval *= valToFind
            result(i) = initialval
        Next
        Return result
    End Function
End Module
namespace StopOrBreak
{
    using System;
    using System.Collections.Concurrent;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Test
    {
        static void Main()
        {
            StopLoop();
            BreakAtThreshold();

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        private static void StopLoop()
        {
            Console.WriteLine("Stop loop...");
            double[] source = MakeDemoSource(1000, 1);
            ConcurrentStack<double> results = new ConcurrentStack<double>();

            // i is the iteration variable. loopState is a 
            // compiler-generated ParallelLoopState
            Parallel.For(0, source.Length, (i, loopState) =>
            {
                // Take the first 100 values that are retrieved
                // from anywhere in the source.
                if (i < 100)
                {
                    // Accessing shared object on each iteration
                    // is not efficient. See remarks.
                    double d = Compute(source[i]);
                    results.Push(d);
                }
                else
                {
                    loopState.Stop();
                    return;
                }

            } // Close lambda expression.
            ); // Close Parallel.For

            Console.WriteLine("Results contains {0} elements", results.Count());
        }


        static void BreakAtThreshold()
        {
            double[] source = MakeDemoSource(10000, 1.0002);
            ConcurrentStack<double> results = new ConcurrentStack<double>();

            // Store all values below a specified threshold.
            Parallel.For(0, source.Length, (i, loopState) =>
            {
                double d = Compute(source[i]);
                results.Push(d);
                if (d > .2)
                {
                    // Might be called more than once!
                    loopState.Break();
                    Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d);
                    Thread.Sleep(1000);
                }
            });

            Console.WriteLine("results contains {0} elements", results.Count());
        }

        static double Compute(double d)
        {
            //Make the processor work just a little bit.
            return Math.Sqrt(d);
        }


        // Create a contrived array of monotonically increasing
        // values for demonstration purposes. 
        static double[] MakeDemoSource(int size, double valToFind)
        {
            double[] result = new double[size];
            double initialval = .01;
            for (int i = 0; i < size; i++)
            {
                initialval *= valToFind;
                result[i] = initialval;
            }

            return result;
        }
    }

}

In einer ParallelFor()-Schleife oder [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1]-Schleife können Sie nicht die gleiche Break- oder Exit-Anweisung verwenden, die in einer sequenziellen Schleife verwendet wird, da diese Sprachkonstrukte nur für Schleifen gültig sind, eine parallele "Schleife" jedoch eigentlich eine Methode und keine Schleife ist. Verwenden Sie stattdessen die Stop-Methode oder die Break-Methode. Einige der Überladungen von Parallel.For lassen Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) in Visual Basic) als Eingabeparameter zu. Das ParallelLoopState-Objekt wird im Hintergrund von der Laufzeit erstellt, und Sie können diesem im Lambda-Ausdruck einen beliebigen Namen zuweisen.

Im folgenden Beispiel erfordert die Methode nur 100 Werte aus der Quellsequenz. Dabei ist es irrelevant, welche Elemente abgerufen wurden. In diesem Fall wird die Stop-Methode verwendet, da durch diese alle Iterationen der Schleife so schnell wie möglich beendet werden, einschließlich den vor der aktuellen Iteration in anderen Threads gestarteten Iterationen.

In der zweiten Methode werden alle Elemente bis zu einem angegebenen Index in der Quellsequenz abgerufen. In diesem Fall wird Break aufgerufen, da beim Erreichen des Index in einem Thread möglicherweise noch vorhergehende Elemente in der Quelle vorhanden ist, die bislang nicht verarbeitet wurden. Beim Verlassen einer Schleife unterbrechen spätere Threads die Verarbeitung nachfolgender Segmente (falls zutreffend) und beenden die Verarbeitung der vorhergehenden Elemente, bevor die Schleife beendet wird.

Beachten Sie, dass nach einem Aufruf von Stop oder Break andere Threads in einer Schleife weiterhin für einen gewissen Zeitraum ausgeführt werden können, der vom Anwendungsentwickler nicht beeinflusst werden kann. Mithilfe der ParallelLoopState.IsStopped-Eigenschaft können Sie überprüfen, ob die Schleife in einem anderen Thread beendet wurde. Wenn im folgenden Beispiel IsStopped zutrifft (true), werden keine weiteren Daten in die Auflistung geschrieben.

Kompilieren des Codes

  • Kopieren Sie das Codebeispiel, und fügen Sie es in ein Visual Studio 2010-Projekt ein.

Siehe auch

Referenz

System.Action<T1, T2>

Konzepte

Datenparallelität (Task Parallel Library)

Parallele Programmierung in .NET Framework

Lambda-Ausdrücke in PLINQ und TPL