共用方式為


如何:使用 Thread-Local 變數撰寫 Parallel.For 迴圈

此範例示範如何使用線程局部變數,在 For 迴圈所建立的每個個別工作中儲存和擷取狀態。 藉由使用線程本機數據,您可以避免同步處理大量共享狀態存取的額外負荷。 您不必在每個反覆專案上寫入共用資源,而是計算並儲存值,直到工作的所有反覆專案都完成為止。 然後,您可以將最終結果一次寫入共用資源,或將它傳遞給另一個方法。

範例

下列範例會呼叫 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 函式來計算具一百萬個元素的陣列中值的總和。 每個元素的值都等於其索引。

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

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

        // Use type parameter to make subtotal a long, not an int
        Parallel.For<long>(0, nums.Length, () => 0,
            (j, loop, subtotal) =>
            {
                subtotal += nums[j];
                return subtotal;
            },
            subtotal => Interlocked.Add(ref total, subtotal));

        Console.WriteLine($"The total is {total:N0}");
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}
'How to: Write a Parallel.For Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForWithThreadLocal

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

        ' Use type parameter to make subtotal a Long type. Function will overflow otherwise.
        Parallel.For(Of Long)(0, nums.Length, Function() 0, Function(j, [loop], subtotal)
                                                                subtotal += nums(j)
                                                                Return subtotal
                                                            End Function, Function(subtotal) Interlocked.Add(total, subtotal))

        Console.WriteLine("The total is {0:N0}", total)
        Console.WriteLine("Press any key to exit")
        Console.ReadKey()
    End Sub

End Module

每個 For 方法的前兩個參數都會指定開始和結束反覆運算值。 在這種方法的重載中,第三個參數是用來初始化本地狀態的位置。 在此情境中,本地狀態是指一個變數,其存活期從該線程上迴圈的第一次迭代前延續到最後一次迭代後。

第三個參數的類型是 Func<TResult>,其中 TResult 是將儲存線程區域狀態的變數類型。 其類型是由呼叫泛型 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 方法時所提供的泛型型別自變數所定義,在此案例中為 Int64。 type 自變數會告知編譯程式將用來儲存線程區域狀態的暫存變數類型。 在此範例中,表達式 () => 0 (或 Visual Basic 中的 Function() 0) 會將線程局部變數初始化為零。 如果泛型型別自變數是參考型別或使用者定義實值型別,表達式看起來會像這樣:

() => new MyClass()  
Function() new MyClass()  

第四個參數會定義循環邏輯。 它必須是 C# 中簽章 Func<int, ParallelLoopState, long, long> 的委派或 Visual Basic 中簽章 Func(Of Integer, ParallelLoopState, Long, Long) 的 Lambda 運算式。 第一個參數是迴圈反覆運算的循環計數器值。 第二個是可用來中斷迴圈的 ParallelLoopState 物件;這個物件由 Parallel 類別提供給迴圈的每次運行。 第三個參數是線程局部變數。 最後一個參數是傳回類型。 在此情況下,類型會 Int64,因為這是我們在 For 類型自變數中指定的類型。 該變數會命名為 subtotal,並由 Lambda 表達式傳回。 傳回值用於在迴圈的每次後續迭代中初始化 subtotal。 您也可以將此最後一個參數視為傳遞至每個迭代的值,然後在最後一次迭代完成時傳遞至 localFinally 委派。

第五個參數會定義在特定線程上完成所有反覆項目之後呼叫一次的方法。 輸入參數的類型會再次對應至 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 方法的類型參數,以及 Lambda 程式碼塊所傳回的類型。 在此範例中,透過呼叫 Interlocked.Add 方法,以執行緒安全的方式將值添加到類別範圍中的變數。 藉由使用線程局部變數,我們已避免在迴圈的每個反覆專案上寫入這個類別變數。

如需如何使用 Lambda 表達式的詳細資訊,請參閱在 PLINQ 和 TPL Lambda 運算式。

另請參閱