方法: スレッド ローカル変数を使用する Parallel.For ループを記述する

次の例に、For ループによって生成される個別のタスクごとの状態を、スレッド ローカル変数を使用して格納および取得する方法を示します。 スレッド ローカル変数を使用することで、共有状態への多数のアクセスを同期するオーバーヘッドを回避できます。 反復処理ごとに共有リソースを作成する代わりに、タスクの反復処理のすべてが完了するまで、値を計算して格納します。 この場合、最終結果を共有リソースに 1 回書き込んだり、別のメソッドに渡したりすることができます。

For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) メソッドを呼び出して、100 万個の要素からなる配列の値の合計を計算する例を次に示します。 各要素の値は、そのインデックスに相当します。

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 {0:N0}", total);
        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 メソッドで、最初の 2 つのパラメーターが最初と最後の反復値を指定します。 メソッドのこのオーバーロードでは、3 番目のパラメーターでローカル状態を初期化します。 このコンテキストでのローカル状態は、現在のスレッドで実行されるループの最初の反復処理の直前から最後の反復処理の直後までの有効期限を持つ変数を意味します。

3 番目のパラメーターの型は Func<TResult> です。ここで、TResult はスレッド ローカル状態を格納する変数の型です。 この型は、ジェネリックの For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) メソッドの呼び出し時に提供されるジェネリック型引数によって定義されます (この例では、Int64)。 型引数は、コンパイラに対し、スレッド ローカル状態を格納するために使用する一時変数の型を指定します。 この例では、式 () => 0 (Visual Basic の場合は Function() 0) でスレッド ローカル変数をゼロに初期化します。 ジェネリック型引数が参照型またはユーザー定義の値型である場合は、以下のような式になります。

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

4 番目のパラメーターは、ループのロジックを定義します。 パラメーター値は、シグネチャが Func<int, ParallelLoopState, long, long> (C# の場合) または Func(Of Integer, ParallelLoopState, Long, Long) (Visual Basic の場合) となっているデリゲートまたはラムダ式でなければなりません。 最初のパラメーターは、ループのその特定の反復処理に対するループ カウンターの値です。 2 番目のパラメーターは、ループを抜けるために使用できる ParallelLoopState オブジェクトです。このオブジェクトは、Parallel クラスによって各ループの発生時に提供されます。 3 番目のパラメーターは、スレッド ローカル変数です。 最後のパラメーターは、戻り値の型です。 この例の場合、型は Int64 型引数で指定されているため、For になります。 その変数の名前は subtotal です。これは、ラムダ式によって返されます。 この戻り値が、ループの次の反復処理で subtotal を初期化するために使用されます。 この最後のパラメーターは、各反復処理に渡されて、最後の反復処理が完了した時点で localFinally デリゲートに渡される値であるとも考えられます。

5 番目のパラメーターが定義するメソッドは、特定のスレッドでのすべての反復処理が完了した時点で 1 回だけ呼び出されます。 この場合も、入力引数の型は For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) メソッドの型引数、および本体のラムダ式によって返される型と一致します。 この例では、スレッド セーフな方法で、この値をクラス スコープで変数に追加するために、Interlocked.Add メソッドを呼び出します。 スレッド ローカル変数を使用することで、このクラス変数をループのすべての反復処理で作成する手間を省きました。

ラムダ式の使用方法の詳細については、「PLINQ および TPL のラムダ式」を参照してください。

関連項目