C# 使用并行ForEach的本地初始化是如何工作的?

C# 使用并行ForEach的本地初始化是如何工作的?,c#,task-parallel-library,C#,Task Parallel Library,我不确定在Parallel.ForEach中使用本地init函数,如msdn文章中所述: Parallel.ForEach(nums,//源集合 ()=>0,//初始化局部变量的方法 (j,循环,小计)=>//循环在每次迭代中调用的方法 { 小计+=nums[j];//修改局部变量 返回小计;//要传递给下一次迭代的值 },... ()=>0如何初始化任何内容?变量的名称是什么?我如何在循环逻辑中使用它?您可以在重载中获得有关MSDN的提示 localInit委托对参与循环执行的每个线程调用一

我不确定在Parallel.ForEach中使用本地init函数,如msdn文章中所述:

Parallel.ForEach(nums,//源集合
()=>0,//初始化局部变量的方法
(j,循环,小计)=>//循环在每次迭代中调用的方法
{
小计+=nums[j];//修改局部变量
返回小计;//要传递给下一次迭代的值
},...

()=>0如何初始化任何内容?变量的名称是什么?我如何在循环逻辑中使用它?

您可以在重载中获得有关MSDN的提示

localInit委托对参与循环执行的每个线程调用一次,并返回每个任务的初始本地状态。这些初始状态传递给每个任务的第一个主体调用。然后,每个后续主体调用返回一个可能修改的状态值,并传递给下一个主体i挑衅

在您的示例中,
()=>0
是一个仅返回
0
的委托,因此此值用于每个任务的第一次迭代,作为对的答案的扩展。Parallel foreach将工作拆分为任务的方式也很重要,它将多个循环迭代分组为单个任务,因此在实践中
localInit()
对于循环的每n次迭代调用一次,可以同时启动多个组

localInit
localFinally
的要点是确保并行foreach循环可以将每个输入的结果组合成单个结果,而无需在
正文中指定lock语句,为此,必须为要创建的值提供初始化(
localInit
)然后每个
body
iteration都可以处理本地值,然后提供一种方法以线程安全的方式组合每个组的值(
localFinally

如果同步任务不需要localInit,则可以使用lambda方法正常引用周围上下文中的值。有关使用localInit/Finally的更深入教程,请参阅,并向下滚动到带有局部值的优化,Joseph Albahari确实是我所有内容的goto来源线程。

参考
并行。ForEach
静态扩展方法:

public static ParallelLoopResult ForEach<TSource, TLocal>(
    IEnumerable<TSource> source,
    Func<TLocal> localInit,
    Func<TSource, ParallelLoopState, TLocal, TLocal> taskBody,
    Action<TLocal> localFinally
)
是一个简单的lambda(匿名函数),它将返回常量整数零。此lambda作为
localInit
参数传递给
Parallel。ForEach
-由于lambda返回整数,因此它具有type
Func
,并且type
TLocal
可以由编译器推断为
int
(类似地,
TSource
可以从作为参数
source
传递的集合类型推断出来)

然后将返回值(0)作为第三个参数(名为
subtotal
)传递给
taskBody
Func
。此(0)用于正文循环的初始种子:

(j, loop, subtotal) =>
{
    subtotal += nums[j]; //modify local variable (Bad idea, see comment)
    return subtotal;     // value to be passed to next iteration
}
第二个lambda(传递给
任务体
)被调用N次,其中N是TPL分区器分配给该任务的项目数

对第二个
任务体
lambda的每次后续调用都将传递新值
小计
,有效地计算此任务正在运行的部分总计。添加分配给此任务的所有项后,将再次调用第三个也是最后一个
localFinally
函数参数,即pass计算从
任务体
返回的
小计
的最终值。由于多个此类任务将并行运行,因此还需要最后一步将所有部分总计相加到最终的“总计”中。但是,由于多个并发任务(在不同线程上)可能会争夺
grandTotal
变量,以线程安全的方式对其进行更改很重要

(我已经更改了MSDN变量的名称以使其更加清晰)

在MS示例中,修改任务体内部的参数subtotal是一种不好的做法,而且是不必要的。例如,代码
subtotal+=nums[j];返回subtotal;
会更好,因为只需
return subtotal+nums[j];
可以缩写为lambda速记投影
(j,loop,subtotal)=>小计+nums[j]

一般来说

localInit/body/localFinally
重载允许在任务执行
taskBody
迭代之前和之后(分别)运行每个任务一次的初始化和清理代码

(注意传递给并行
For
/
Foreach
的For范围/可枚举性将被划分为批
IEnumerable
,每个批将分配一个任务)

每个任务中,
localInit
将被调用一次,
主体
代码将被重复调用,每批每项调用一次(
0..N
次),完成后将调用一次
localFinally

此外,您可以通过
TLocal
返回值从
localInit Func
传递任务持续时间内所需的任何状态(即传递给
taskBody
localFinally
委托),该返回值来自
localInit Func
——我在下面调用了该变量
taskmarals

常用的“localInit”:

  • 创建和初始化循环体所需的昂贵资源,如数据库连接或web服务连接
  • 保留任务局部变量以保存(非争用)正在运行的总计或集合
  • 如果需要将多个对象从
    localInit
    返回到
    taskBody
    localFinally
    ,则可以
    () => 0, // method to initialize the local variable
    
    (j, loop, subtotal) =>
    {
        subtotal += nums[j]; //modify local variable (Bad idea, see comment)
        return subtotal;     // value to be passed to next iteration
    }
    
    long grandTotal = 0;
    Parallel.ForEach(nums,            // source collection
      () => 0,                        // method to initialize the local variable
      (j, loop, subtotal) =>          // method invoked by the loop on each iteration
         subtotal + nums[j],          // value to be passed to next iteration subtotal
      // The final value of subtotal is passed to the localFinally function parameter
      (subtotal) => Interlocked.Add(ref grandTotal, subtotal)
    
    public void MyParallelizedMethod()
    {
        // Shared variable. Not thread safe
        var itemCount = 0; 
    
        Parallel.For(myEnumerable, 
        // localInit - called once per Task.
        () => 
        {
           // Local `task` variables have no contention 
           // since each Task can never run by multiple threads concurrently
           var sqlConnection = new SqlConnection("connstring...");
           sqlConnection.Open();
    
           // This is the `task local` state we wish to carry for the duration of the task
           return new 
           { 
              Conn = sqlConnection,
              RunningTotal = 0
           }
        },
        // Task Body. Invoked once per item in the batch assigned to this task
        (item, loopState, taskLocals) =>
        {
          // ... Do some fancy Sql work here on our task's independent connection
          using(var command = taskLocals.Conn.CreateCommand())
          using(var reader = command.ExecuteReader(...))
          {
            if (reader.Read())
            {
               // No contention for `taskLocal`
               taskLocals.RunningTotal += Convert.ToInt32(reader["countOfItems"]);
            }
          }
          // The same type of our `taskLocal` param must be returned from the body
          return taskLocals;
        },
        // LocalFinally called once per Task after body completes
        // Also takes the taskLocal
        (taskLocals) =>
        {
           // Any cleanup work on our Task Locals (as you would do in a `finally` scope)
           if (taskLocals.Conn != null)
             taskLocals.Conn.Dispose();
    
           // Do any reduce / aggregate / synchronisation work.
           // NB : There is contention here!
           Interlocked.Add(ref itemCount, taskLocals.RunningTotal);
        }
    
    class Program
    {
        class Person
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
        }
    
        static List<Person> GetPerson() => new List<Person>()
        {
            new Person() { Id = 0, Name = "Artur", Age = 26 },
            new Person() { Id = 1, Name = "Edward", Age = 30 },
            new Person() { Id = 2, Name = "Krzysiek", Age = 67 },
            new Person() { Id = 3, Name = "Piotr", Age = 23 },
            new Person() { Id = 4, Name = "Adam", Age = 11 },
        };
    
        static void Main(string[] args)
        {
            List<Person> persons = GetPerson();
            int ageTotal = 0;
    
            Parallel.ForEach
            (
                persons,
                () => 0,
                (person, loopState, subtotal) => subtotal + person.Age,
                (subtotal) => Interlocked.Add(ref ageTotal, subtotal)
            );
    
            Console.WriteLine($"Age total: {ageTotal}");
            Console.ReadKey();
        }
    }