How to: Write a Parallel.For Loop That Has Thread-Local Variables

This example shows how to use thread-local variables to store and retrieve state in each separate task that is created by a For loop. By using thread-local data, you can avoid the overhead of synchronizing a large number of accesses to shared state. Instead of writing to a shared resource on each iteration, you compute and store the value until all iterations for the task are complete. You can then write the final result once to the shared resource, or pass it to another method.

Example

'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();
        }
    }
}

The first two parameters of every For method specify the beginning and ending iteration values. In this overload of the method, the third parameter is where you initialize your local state. "Local state" in this context means a variable whose lifetime extends from just prior to the first iteration of the loop on the current thread, to just after the last iteration.

The type of the third parameter is a Func<TResult> where TResult is the type of the variable that will store the thread-local state. Note that in this example, a generic version of the method is used, and type parameter is long (Long in Visual Basic) The type parameter tells the compiler the type of the temporary variable that will be used to store the thread-local state. The expression () => 0 (Function() 0 in Visual Basic) in this example means that the thread-local variable is initialized to zero. If the type parameter is a reference type or user-defined value type, then this Func would look like this:

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

The fourth type parameter is where you define the loop logic. IntelliSense shows that it has a type of Func<int, ParallelLoopState, long, long> or Func(Of Integer, ParallelLoopState, Long, Long). The lambda expression expects three input parameters in that same order corresponding to those types. The last type parameter is the return type. In this case the type is long because that is the type we specified in the For type parameter. We call that variable subtotal in the lambda expression, and return it. The return value is used to initialize subtotal on each subsequent iteration. You can also think of this last parameter simply as a value that is passed to each iteration, and then to the localFinally delegate when the last iteration is complete.

The fifth parameter is where you define the method that will be called one time, after all the iterations on this thread have completed. The type of the input parameter again corresponds to the type parameter of the For method and the type returned by the body lambda expression. In this example, the value is added to a variable at class scope in a thread safe way. By using a thread-local variable, we have avoided writing to this class variable on every iteration of every thread.

For more information about how to use lambda expressions, see Lambda Expressions in PLINQ and TPL.

See Also

Concepts

Task Parallel Library

Lambda Expressions in PLINQ and TPL

Other Resources

Data Parallelism (Task Parallel Library)

Parallel Programming in the .NET Framework