Практическое руководство. Остановка цикла Parallel.For или выход из этого цикла

В следующем примере показано, как использовать break (или Exit в Visual Basic), чтобы разорвать цикл For, а также как остановить выполнение цикла. В этом контексте "разорвать"означает завершение всех итераций во всех потоках до текущей итерации в текущем потоке с последующим выходом из цикла. " Остановить" означает остановку всех итераций как можно раньше.

Пример

В примере показана работа цикла For; однако остановить или прервать цикл ForEach можно таким же образом. В цикле ForEach индекс итерации создается внутренне для каждого элемента и каждого раздела.

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

}

В цикле ParallelFor() или [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1] нельзя использовать тот же оператор break или Exit, который используется в последовательном цикле, поскольку эти языковые конструкции языка допустимы для циклов, а параллельный "цикл" фактически является методом, а не циклом. Вместо этого используйте метод Stop или метод Break. Некоторые перегрузки Parallel.For принимают Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) в Visual Basic) в качестве входного параметра. Объект ParallelLoopState создается в фоновом режиме средой выполнения, и ему можно присвоить любое имя в лямбда-выражении.

В следующем примере метод требует только 100 значений из исходной последовательности, и неважно, какие элементы были извлечены. В данном случае используется метод Stop, поскольку он указывает как можно раньше остановить выполнение всех итераций цикла, включая начатые до текущей итерации в других потоках.

Во втором методе извлекаются все элементы до указанного индекса в исходной последовательности. В этом случае вызывается метод Break, поскольку по достижении индекса в одном потоке может получиться так, что предыдущие элементы в источнике еще не были обработаны. Разрыв приведет к тому, что другие потоки прекратят выполнение последующих работ (при их наличии) и завершат обработку всех предыдущих элементов до выхода из цикла.

Важно понимать, что после вызова метода Stop или метода Break выполнение других потоков в цикле может продолжиться в течение некоторого периода времени, что не может контролировать разработчик приложения. Свойство ParallelLoopState.IsStopped можно использовать для проверки того, был ли остановлен цикл в другом потоке. В следующем примере, если свойство IsStopped имеет значение true, дальнейшие сведения не записываются в коллекцию.

Компиляция кода

  • Скопируйте и вставьте пример кода в проект Visual Studio 2010.

См. также

Ссылки

System.Action<T1, T2>

Основные понятия

Параллелизм данных (библиотека параллельных задач)

Параллельное программирование в .NET Framework

Лямбда-выражения в PLINQ и библиотеке параллельных задач