如何:编写具有线程本地变量的 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 方法的前两个参数都指定起始迭代值和结束迭代值。 在方法的此重载中,第三个参数是在其中初始化本地状态的参数。" 此上下文中的“本地状态”是指其生存期恰好从当前线程上循环的第一个迭代之前延伸至最后一个迭代之后的变量。

第三个参数的类型为 Func<TResult>,其中 TResult 是将存储线程本地状态的变量的类型。 请注意,在此示例中使用了方法的泛型版本,并且类型参数为 long(在 Visual Basic 中为 Long)。类型参数告知编译器将要用于存储线程本地状态的临时变量的类型。 此示例中的 () => 0(在 Visual Basic 中为 Function() 0)表达式表示线程本地变量的初始值为零。 如果类型参数是引用类型或用户定义的值类型,则此 Func 将如下所示:

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

第四个类型参数是在其中定义循环逻辑的参数。 IntelliSense 显示其类型为 Func<int, ParallelLoopState, long, long> 或 Func(Of Integer, ParallelLoopState, Long, Long)。 lambda 表达式按对应于这些类型的相同顺序需要三个输入参数。 最后一个类型参数是返回类型。 在这种情况下,类型为 long,因为该类型是我们在 For 类型参数中指定的类型。 我们在 lambda 表达式中调用该变量 subtotal,并将其返回。 返回值用于在每个后续的迭代上初始化小计。 您也可以将此最后一个参数简单地看作传递到每个迭代,然后在最后一个迭代完成时传递到 localFinally 委托的值。

第五个参数是在其中定义方法的参数,当此线程上的所有迭代均已完成后,将调用该方法一次。 输入参数的类型同样也对应于 For 方法的类型参数,以及主体 lambda 表达式返回的类型。 在此示例中,将采用线程安全的方式在类范围将值添加到变量。 通过使用线程本地变量,我们避免了在每个线程的每个迭代上写入此类变量。

有关如何使用 lambda 表达式的更多信息,请参见在 PLINQ 和 TPL 中的 Lambda 表达式

请参见

概念

数据并行(任务并行库)

.NET Framework 中的并行编程

任务并行库

在 PLINQ 和 TPL 中的 Lambda 表达式