HOW TO:停止或中斷 Parallel.For 迴圈

下列範例顯示如何從 For 迴圈中 break (中斷) (在 Visual Basic 中為 Exit),也示範如何停止迴圈。 在此內容中,「中斷」表示讓在目前執行緒上之目前反覆項目之前的所有執行緒上的所有反覆項目完成,然後結束迴圈。 「停止」表示方便時盡快停止所有反覆項目。

範例

本範例會示範 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] 迴圈中,您無法像在使用循序迴圈一樣使用 breakExit 陳述式,因為這些語言建構只對迴圈有效,而平行的「迴圈」實際上是方法,並不是迴圈。 您應該改用 StopBreak 方法。 Parallel.For 的部分多載可接受 Action<int, ParallelLoopState> (在 Visual Basic 中為 Action(Of Integer, ParallelLoopState)) 做為輸入參數。 ParallelLoopState 物件是由執行階段在幕後建立,您可以在 Lambda 運算式中指定任何喜歡的名稱給它。

下列範例中的方法只需要擷取來源序列中的 100 個值,而擷取的是哪些項目並不重要。 此案例中會使用 Stop 方法,因為它會告訴迴圈的所有反覆項目 (包括在其他執行緒上比目前反覆項目早開始的反覆項目) 方便時盡快停止。

在第二個方法中,我們會擷取來源序列中截至指定索引位置以前的所有項目。 此案例中會呼叫 Break,因為當我們到達其中一個執行緒上的這個索引位置時,來源中之前的項目可能尚未受到處理。 Break 會讓其他執行緒放棄之後區段上的工作 (如果有的話) 並完成處理所有之前的項目,然後再結束迴圈。

請務必了解,在呼叫 StopBreak 之後,迴圈上的其他執行緒可能會繼續執行一段時間,應用程式開發人員無法控制這一點。 您可以使用 ParallelLoopState.IsStopped 屬性,檢查另一個執行緒上的這個迴圈是否已停止。 在下列範例中,如果 IsStopped 為 true,則不會再將資料寫入集合。

編譯程式碼

  • 將程式碼範例複製並貼到 Visual Studio 2010 專案中。

請參閱

參考

System.Action<T1, T2>

概念

資料平行處理原則 (工作平行程式庫)

以 .NET Framework 進行平行程式設計

PLINQ 和 TPL 中的 Lambda 運算式