我不确定如何在Parallel.ForEach中使用本地init函数,如msdn文章中所述:http://msdn.microsoft.com/en-us/library/dd997393.aspx
Parallel.ForEach<int, long>(nums, // source collection
() => 0, // method to initialize the local variable
(j, loop, subtotal) => // method invoked by the loop on each iteration
{
subtotal += nums[j]; //modify local variable
return subtotal; // value to be passed to next iteration
},...
()=&gt; 0初始化什么?变量的名称是什么以及如何在循环逻辑中使用它?
答案 0 :(得分:25)
参考Parallel.ForEach
静态扩展方法的following overload:
public static ParallelLoopResult ForEach<TSource, TLocal>(
IEnumerable<TSource> source,
Func<TLocal> localInit,
Func<TSource, ParallelLoopState, TLocal, TLocal> taskBody,
Action<TLocal> localFinally
)
在您的具体示例中
该行:
() => 0, // method to initialize the local variable
只是一个lambda(匿名函数),它将返回常量整数零。这个lambda作为localInit
参数传递给Parallel.ForEach
- 因为lambda返回一个整数,它的类型为Func<int>
,类型TLocal
可以推断为int
由编译器(类似地,TSource
可以从作为参数source
传递的集合的类型推断出来)
然后将返回值(0)作为第3个参数(名为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(传递给taskBody
)被称为N次,其中N是TPL分区器分配给该任务的项目数。
对第二个taskBody
lambda的每次后续调用都将传递subTotal
的新值,有效地计算此任务的正在运行的部分总计。在添加了分配给此任务的所有项目之后,将再次调用第三个和最后一个localFinally
函数参数,并传递从subtotal
返回的taskBody
的最终值。由于几个这样的任务将并行运行,因此还需要最后一步将所有部分总数加入到最终的“盛大”中。总。但是,因为多个并发任务(在不同的线程上)可能会争用grandTotal
变量,所以对它的更改必须以线程安全的方式完成。
(我已更改MSDN变量的名称以使其更清晰)
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)
在MS示例中,修改任务主体内部的参数小计是一种不好的做法,而且是不必要的。即代码subtotal += nums[j]; return subtotal;
会更好,因为return subtotal + nums[j];
可以缩写为lambda速记投影(j, loop, subtotal) => subtotal + nums[j]
一般
Parallel.For / Parallel.ForEach的localInit / body / localFinally
重载允许每次执行任务初始化和清理代码,分别在taskBody
次迭代之前和之后运行由任务执行。
(注意传递给并行For
/ Foreach
的For range / Enumerable将被分区为IEnumerable<>
批,其中每个都将分配一个任务)
在每个任务中,localInit
将被调用一次,body
代码将被重复调用,每个项目批量调用一次(0..N
次),完成后将调用localFinally
。
此外,您可以通过{{1}的通用taskBody
返回值传递任务期间所需的任何状态(即localFinally
和TLocal
委托)我在下面调用了这个变量localInit Func
。
&#34; localInit&#34;的常用用途:
taskLocals
返回到localInit
和taskBody
,则可以使用强类型类localFinally
或者,如果您对Tuple<,,>
只使用lambdas,你也可以通过匿名类传递数据。请注意,如果您使用localInit / taskBody / localFinally
的返回值来共享多个任务中的引用类型,则需要考虑此对象的线程安全性 - 不可变性。&#34; localFinally&#34;的常用用途动作:强>
localInit
中使用的IDisposables
等资源(例如数据库连接,文件句柄,Web服务客户端等)taskLocals
,如整数Interlocked.Increment
或类似的 lock
是循环操作的taskBody
部分 - 您希望针对性能优化此功能。
最好用评论的例子总结这一点:
tight
还有更多例子:
答案 1 :(得分:7)
作为@Honza Brestan答案的延伸。 Parallel foreach将工作分成任务的方式也很重要,它会将几个循环迭代分组到一个任务中,因此在实践中localInit()
会在循环的每n次迭代中调用一次,并且可以同时启动多个组。
localInit
和localFinally
的要点是确保并行的foreach循环可以将每次迭代的结果合并为单个结果,而无需在body
中指定锁定语句要执行此操作,您必须为要创建的值(localInit
)提供初始化,然后每个body
itteration可以处理本地值,然后您提供一种方法来组合每个组的值({ {1}})以线程安全的方式。
如果您不需要localInit来同步任务,您可以使用lambda方法正常引用周围上下文中的值而不会出现任何问题。有关使用localInit / Finally的更深入的教程以及向下滚动到使用本地值进行优化,请参阅Threading in C# (Parallel.For and Parallel.ForEach),Joseph Albahari确实是我所有线程的goto源代码。
答案 2 :(得分:2)
您可以在 correct Parallel.ForEach
重载中获得有关MSDN的提示。
对参与循环执行的每个线程调用一次localInit委托,并为每个任务返回初始本地状态。这些初始状态将传递给每个任务的第一个正文调用。然后,每个后续的主体调用都会返回一个可能已修改的状态值,该值将传递给下一个主体调用。
在您的示例中,() => 0
是一个仅返回0
的委托,因此该值用于每项任务的第一次迭代。
答案 3 :(得分:0)
从我这边稍微简单一点的例子
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();
}
}