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

この例では、スレッド ローカル変数を使用して、For ループで作成された独立したタスクごとに、状態を格納および取得する方法を示します。 スレッド ローカル データを使用することで、共有状態への多数のアクセスが同期する際のオーバーヘッドを回避できます。 反復ごとに共有リソースに書き込む代わりに、タスクのすべての反復処理が完了するまで値を計算して保存します。 最終的な結果は、共有リソースに一度書き込むか、またはその他のメソッドに渡すことができます。

使用例

'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(x) Interlocked.Add(total, x))

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

End Module
namespace ThreadLocalFor
{
    using System;
    using System.Collections.Generic;
    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;

            // 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;
            },
                (x) => Interlocked.Add(ref total, x)
            );

            Console.WriteLine("The total is {0}", total);
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
}

For メソッドごとに、最初の 2 つのパラメーターで、開始と終了の反復値を指定します。 メソッドのこのオーバーロードでは、3 つ目のパラメーターは、ローカル状態を初期化した場所です。 " ローカル状態" とは、有効期間が現在のスレッドにおけるループの最初の反復前から、最後の反復後まで延長された変数を意味します。

3 つ目のパラメーターの型は Func<TResult> です。TResult は、スレッド ローカルの状態を格納する変数の型です。 この例では、メソッドのジェネリック バージョンが使用されており、型パラメーターは long (Visual Basic では Long) です。型パラメーターにより、スレッド ローカルの状態を格納するために使用する一時変数の型がコンパイラに通知されます。 この例の式 () => 0 (Visual Basic では Function() 0) は、スレッド ローカル変数がゼロに初期化されることを意味します。 型パラメーターが参照型、またはユーザー定義の値の型である場合、この Func は次のようになります。

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

4 つ目の型パラメーターは、ループのロジックを定義した場所です。 IntelliSense には、Func<int, ParallelLoopState, long, long> または Func(Of Integer, ParallelLoopState, Long, Long) の型があることが示されます。 ラムダ式では、3 つの入力パラメーターがこれらの型に対応する順序と同じであることが求められます。 最後の型パラメーターは戻り値の型です。 この場合、型は For 型パラメーターで指定した型である long です。 ラムダ式の subtotal 変数を呼び出して、値を取得します。 戻り値は、後続の反復ごとに小計を初期化する際に使用されます。 また、この最後のパラメーターは、単に最後の反復が完了したときに各反復処理に渡され、その後で localFinally デリゲートに渡される値として考えることもできます。

5 つ目のパラメーターでは、このスレッド上のすべての反復処理が完了した後に 1 度呼び出されるメソッドを定義しています。 For メソッドと型の型パラメーターに対応する入力パラメーターの型が、再度本体のラムダ式によって返されます。 この例では、スレッド セーフのクラス スコープで変数に追加されます。 スレッド ローカル変数を使用することで、各スレッドの繰り返しごとにこのクラス変数に記述しなくて済むようにしています。

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

参照

概念

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

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

タスク並列ライブラリ

PLINQ および TPL のラムダ式