Практическое руководство. Остановка цикла 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
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;
    }
}

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

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

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

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

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

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

См. также

Ссылки

System.Action<T1, T2>

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

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

Другие ресурсы

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

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