Share via


Cómo: Detener o interrumpir un bucle Parallel.For

En el siguiente ejemplo se muestra cómo interrumpir un bucle For (o Salir de él en Visual Basic) y también cómo detener un bucle. En este contexto, "interrumpir" significa completar todas las iteraciones en todos los subprocesos que son anteriores a la iteración actual en el subproceso actual y, a continuación, salir del bucle. " Detener" significa detener todas las iteraciones en cuanto sea conveniente.

Ejemplo

En este ejemplo se muestra un bucle For; sin embargo, se puede detener o interrumpir desde un bucle ForEach de la misma manera. En un bucle ForEach, se genera un índice de iteración internamente para cada uno de los elementos de cada partición.

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

}

En un bucle ParallelFor() u [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1], no se puede usar la misma instrucción break o Exit que se utiliza en un bucle secuencial porque estas construcciones de lenguaje son válidas para los bucles, y un "bucle" paralelo es realmente un método, no un bucle. En su lugar, se usan los métodos Break o Stop. Algunas de las sobrecargas de Parallel.For aceptan Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) en Visual Basic) como parámetro de entrada. El runtime crea en segundo plano el objeto ParallelLoopState, al que puede dar cualquier nombre que desee en la expresión lambda.

En el siguiente ejemplo, el método requiere sólo 100 valores de la secuencia de origen y no importa qué elementos se recuperan. En este caso se usa el método Stop, porque indica todas las iteraciones del bucle (incluidas las que comenzaron antes de la iteración actual en otros subprocesos), para detenerse en cuanto sea conveniente.

En el segundo método se recuperan todos los elementos hasta un índice especificado en la secuencia de origen. En este caso, se llama a Break, porque cuando se llega al índice en un subproceso, es posible que todavía no se hayan procesado los elementos anteriores en el origen. La interrupción hará que otros subprocesos abandonen el trabajo en segmentos posteriores (si están ocupados en alguno) y que completen el procesamiento de todos los elementos anteriores antes de salir del bucle.

Es importante entender que después de llamar a Stop o Break, otros subprocesos en un bucle pueden seguir ejecutándose durante algún tiempo, pero esto no está bajo el control del desarrollador de la aplicación. Puede usar la propiedad ParallelLoopState.IsStopped para comprobar si el bucle se ha detenido en otro subproceso. En el siguiente ejemplo, si IsStopped es true, no se escriben más datos en la colección.

Compilar el código

  • Copie y pegue el ejemplo de código en un proyecto de Visual Studio 2010.

Vea también

Referencia

System.Action<T1, T2>

Conceptos

Paralelismo de datos (Task Parallel Library)

Programación paralela en .NET Framework

Expresiones lambda en PLINQ y TPL