如何:撰寫含有分割區域變數的 Parallel.ForEach 迴圈

下列範例說明如何撰寫使用分割區域變數的 ForEach 方法。 當 ForEach 迴圈執行時,它會將其來源集合分成多個分割。 每個分割會有自己的分割區域變數複本。 分割區域變數與執行緒區域變數類似,不同之處在於多個分割可以在單一執行緒上執行。

這個範例中的程式碼和參數非常類似於對應的 For 方法。 如需詳細資訊,請參閱:如何:撰寫含有執行緒區域變數的 Parallel.For 迴圈

若要在 ForEach 迴圈中使用分割區域變數,您必須呼叫使用兩個型別參數的其中一個方法多載。 第一個型別參數 TSource 指定來源項目的類型,而第二個型別參數 TLocal 則指定分割區域變數的類型。

範例

下列範例呼叫 Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 多載,以計算一百萬個項目之陣列的總和。 此多載包含四個參數:

  • source,這是資料來源。 它必須實作 IEnumerable<T>。 在我們的範例中,資料來源是 IEnumerable<Int32> 方法傳回的一百萬個成員 Enumerable.Range 物件。

  • localInit,或初始化分割區域變數的函式。 此函式會針對 Parallel.ForEach 作業執行所在的每個分割呼叫一次。 我們的範例會將分割區域變數初始化為零。

  • body,這是平行迴圈在迴圈的每個反覆項目上叫用的 Func<T1,T2,T3,TResult>。 其簽章為 Func\<TSource, ParallelLoopState, TLocal, TLocal>。 您會提供委派的程式碼,而迴圈會傳入輸入參數,包括:

    您的委派會傳回分割區域變數,該變數接著會傳遞給該特定分割中所執行之迴圈的下一個反覆項目。 每個迴圈分割會保留此變數的個別執行個體。

    在這個範例中,委派會將每個整數的值加入至分割區域變數,以維護該分割中整數值項目的累積總計。

  • localFinally,這是當每個分割的迴圈作業完成時,Action<TLocal> 所叫用的 Parallel.ForEach 委派。 Parallel.ForEach 方法會將此迴圈分割之分割區域變數的最後一個值傳遞給 Action<TLocal> 委派,而您會提供程式碼,以執行為了合併此分割中的結果與其餘分割中的結果所需的動作。 這個委派可由多個工作同時叫用。 因此,此範例使用 Interlocked.Add(Int32, Int32) 方法來同步處理對 total 變數的存取。 由於委派類型是 Action<T>,因此不會有傳回值。

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        int[] nums = Enumerable.Range(0, 1000000).ToArray();
        long total = 0;

        // First type parameter is the type of the source elements
        // Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach<int, long>(
            nums, // source collection
            () => 0, // method to initialize the local variable
            (j, loop, subtotal) => // method invoked by the loop on each iteration
            {
                subtotal += j; //modify local variable
                return subtotal; // value to be passed to next iteration
            },
            // Method to be executed when each partition has completed.
            // finalResult is the final value of subtotal for a particular partition.
            (finalResult) => Interlocked.Add(ref total, finalResult));

        Console.WriteLine("The total from Parallel.ForEach is {0:N0}", total);
    }
}
// The example displays the following output:
//        The total from Parallel.ForEach is 499,999,500,000
' How to: Write a Parallel.ForEach Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForEachThreadLocal
    Sub Main()

        Dim nums() As Integer = Enumerable.Range(0, 1000000).ToArray()
        Dim total As Long = 0

        ' First type parameter is the type of the source elements
        ' Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
                                           Function(elem, loopState, subtotal)
                                               subtotal += elem
                                               Return subtotal
                                           End Function,
                                            Sub(finalResult)
                                                Interlocked.Add(total, finalResult)
                                            End Sub)

        Console.WriteLine("The result of Parallel.ForEach is {0:N0}", total)
    End Sub
End Module
' The example displays the following output:
'       The result of Parallel.ForEach is 499,999,500,000

另請參閱