Share via


Procedura: arrestare o interrompere un ciclo Parallel.For

Nell'esempio seguente viene mostrato come uscire da un ciclo For mediante l'istruzione break (o Exit in Visual Basic) nonché come arrestare un ciclo. In questo contesto, "interrompere" significa completare tutte le iterazioni in tutti i thread precedenti all'iterazione corrente nel thread corrente e quindi uscire dal ciclo. " Arrestare" significa invece arrestare tutte le iterazioni non appena risulta appropriato.

Esempio

In questo esempio viene illustrato un ciclo For; è comunque possibile arrestare o interrompere un ciclo ForEach nello stesso modo. In un ciclo ForEach, viene generato internamente un indice di iterazione per ogni elemento in ogni partizione.

' 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 un ciclo ParallelFor() o [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1] non è possibile utilizzare la stessa istruzione break o Exit utilizzata in un ciclo sequenziale poiché questi costrutti di linguaggio sono validi per i cicli, e un "ciclo" parallelo in realtà è un metodo, non un ciclo. Al posto di queste istruzioni si utilizzano invece i metodi Stop e Break. Alcuni degli overload di Parallel.For accettano un oggetto Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) in Visual Basic) come parametro di input. L'oggetto ParallelLoopState viene creato automaticamente dal runtime ed è possibile assegnarvi il nome desiderato nell'espressione lambda.

Nell'esempio seguente il metodo richiede solo 100 valori della sequenza di origine senza dare importanza a quali elementi vengono recuperati. In questo caso si utilizza il metodo Stop poiché indica a tutte le iterazioni del ciclo, comprese quelle iniziate prima dell'iterazione corrente in altri thread, di arrestarsi non appena risulta appropriato.

Nel secondo metodo si recuperano gli elementi della sequenza di origine dal primo fino a raggiungere un indice specificato. In questo caso si chiama Break, poiché quando si raggiunge l'indice in un determinato thread è possibile che alcuni elementi precedenti dell'origine non siano stati ancora elaborati. Una volta chiamato il metodo Break, gli altri thread non eseguiranno le operazioni eventualmente previste per i segmenti successivi e completeranno l'elaborazione di tutti gli elementi precedenti prima di uscire dal ciclo.

È importante tenere presente che dopo una chiamata a Stop o Break è possibile che altri thread in un ciclo continuino a essere in esecuzione per un periodo di tempo non controllabile dallo sviluppatore dell'applicazione. È possibile utilizzare la proprietà ParallelLoopState.IsStopped per controllare se il ciclo è stato arrestato in un altro thread. Nell'esempio seguente, se IsStopped è true, nell'insieme non vengono scritti altri dati.

Compilazione del codice

  • Copiare e incollare l'esempio di codice in un progetto Visual Studio 2010.

Vedere anche

Riferimenti

System.Action<T1, T2>

Concetti

Parallelismo dei dati (Task Parallel Library)

Programmazione parallela in .NET Framework

Espressioni lambda in PLINQ e TPL