C# 使用并行ForEach的本地初始化是如何工作的?
我不确定在Parallel.ForEach中使用本地init函数,如msdn文章中所述: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(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返回整数,因此它具有typeFunc
,并且typeTLocal
可以由编译器推断为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();
}
}