Comment : arrêter ou quitter une boucle Parallel.For

L'exemple suivant indique comment quitter une boucle For (break ou Exit en Visual Basic) et également comment arrêter une boucle. Dans ce contexte, « quitter » signifie terminer toutes les itérations de tous les threads précédant l'itération actuelle sur le thread actuel, puis quitter la boucle. " « Arrêter » signifie arrêter toutes les itérations dès que possible.

Exemple

Cet exemple montre une boucle For ; toutefois, vous pouvez l'arrêter ou la rompre de la même façon qu'une boucle ForEach. Dans une boucle ForEach, un index d'itération est généré en interne pour chaque élément de chaque partition.

' 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;
        }
    }

}

Dans une boucle ParallelFor() ou [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1], vous ne pouvez pas utiliser la même instruction break ou Exit utilisée dans une boucle séquentielle car ces constructions de langage sont valides pour les boucles mais une « boucle » parallèle est en réalité une méthode et non une boucle. À la place, utilisez les méthodes Stop ou Break. Certaines des surcharges de Parallel.For acceptent un Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) en Visual Basic) comme paramètre d'entrée. L'objet ParallelLoopState est créé par l'exécution en arrière-plan et vous pouvez lui donner le nom de votre choix dans l'expression lambda.

Dans l'exemple suivant, la méthode requiert uniquement 100 valeurs de la séquence source et les éléments récupérés n'ont pas d'importance. Dans ce cas, la méthode Stop est utilisée, car elle dit à toutes les itérations de la boucle, notamment celles commencées avant l'itération actuelle sur d'autres threads, de s'arrêter dès que possible.

Dans la deuxième méthode, tous les éléments jusqu'à un index spécifié dans la séquence source sont récupérés. Dans ce cas, Break est appelée, car lorsque l'index d'un thread est atteint, il est possible que les éléments antérieurs de la source n'aient pas encore été traités. Le fait de quitter entraînera l'abandon du travail des autres threads sur les segments suivants (le cas échéant) et terminera le traitement de tous les éléments antérieurs avant de quitter la boucle.

Il est important de comprendre qu'après l'appel de Stop ou Break, les autres threads d'une boucle peuvent continuer à fonctionner pendant un certain temps, ce à quoi le développeur d'applications ne peut rien faire. Vous pouvez utiliser la propriété ParallelLoopState.IsStopped pour vérifier si la boucle a été arrêtée sur un autre thread. Dans l'exemple suivant, si IsStopped est true, aucune donnée supplémentaire n'est écrite dans la collection.

Compilation du code

  • Copiez et collez l'exemple de code dans un projet Visual Studio 2010.

Voir aussi

Référence

System.Action<T1, T2>

Concepts

Parallélisme de données (bibliothèque parallèle de tâches)

Programmation parallèle dans le .NET Framework

Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches