方法: Parallel.For ループを停止または中断する

次の例では、For ループを break (Visual Basic では Exit) する方法とループを停止する方法について示します。 このコンテキストでいう "break (中断)" とは、現在のスレッド上の現在の反復処理より前に、すべてのスレッドですべての反復処理を完了し、ループを終了することを意味します。"Stop (停止)" は、都合が付きしだい、すべての反復処理を停止することを意味します。

使用例

次の例では、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> (Visual Basic では Action(Of Integer, ParallelLoopState)) を入力パラメーターとして受け入れるものがあります。 実行時に背後で作成される ParallelLoopState オブジェクトには、ラムダ式を使って任意の名前を付けることができます。

この例では、StopLoop() メソッドがソース シーケンスから必要なのは 100 の値のみで、どの要素が取得されるかは重要ではありません。 この場合、Stop メソッドが使用されます。他のスレッド上の現在の反復処理の前に開始されたループを含む、すべての反復に指示を伝え、都合が付きしだい停止させるためです。

BreakAtThreshold() メソッドでは、ソース シーケンス内の指定されたインデックスまでのすべての要素を抽出します。 この場合、Break が呼び出されます。1 つのスレッドでインデックスに達したときに、ソース内の直前の要素がまだ処理されていない可能性があるためです。 Break は、他のスレッドが以降のセグメントでの作業を (処理中であっても) 中止し、直前のすべての要素の処理を完了してからループを終了させます。

Stop または Break のどちらかが呼び出された後に、ループの他のスレッドが引き続き実行されるかどうかを制御できないことに注意してください。 ParallelLoopState.IsStopped プロパティを使用して、ループが他のスレッドで停止したかどうかを確認できます。

コードのコンパイル

  • コード例をコピーして Visual Studio プロジェクトに貼り付けます。

参照

関連項目

System.Action<T1, T2>

概念

PLINQ および TPL のラムダ式

その他の技術情報

データの並列化 (タスク並列ライブラリ)

.NET Framework の並列プログラミング