当前位置: 首页 > 编程语言 > C#

ParallelForEach的本地初始化是如何工作的?分享

时间:2023-04-10 20:55:09 C#

ParallelForEach的本地初始化是如何工作的?我不确定在Parallel.ForEach中使用本地初始化函数,如msdn文章中所述:http://msdn.microsoft.com/en-us/library/dd997393.aspxParallel.ForEach(nums,//sourcecollection()=>0,//初始化局部变量的方法(j,loop,subtotal)=>//每次迭代循环调用的方法{subtotal+=nums[j];//修改局部变量returnsubtotal;//传递给下一次迭代的值},...()=>0我如何初始化任何东西?变量的名称是什么,我如何在循环逻辑中使用它?参考下面的重载Parallel.ForEach静态扩展方法:publicstaticParallelLoopResultForEach(IEnumerablesource,FunclocalInit,FunctaskBody,ActionlocalFinally)在您的具体示例中,这一行:()=>0,//初始化局部变量的方法只是一个lambda(匿名函数)将返回常量整数零。此lambda作为localInit参数传递给Parallel.ForEach-因为lambda返回一个整数,它是Func类型,并且TLocal类型可以由编译器推断为是int(同样,TSource可以从类型推断)集合作为参数source传递)然后将返回值(0)作为第3个参数(命名为小计)传递给taskBodyFunc。这个(0)用于主体循环的初始种子:(j,loop,subtotal)=>{subtotal+=nums[j];//修改局部变量(坏主意,见评论)返回小计;//要传递给下一次迭代的值}第二个lambda(传递给taskBody)被调用N次,其中N是TPL分区程序分配给任务的项目数。对第二个taskBodylambda的每次后续调用都将传递taskBody的新值,有效地计算此Task的运行部件总数。添加分配给此任务的所有项目后,再次调用第三个也是最后一个localFinally函数参数,再次传递taskBody返回的小计的最终值。由于这些任务中的几个将并行运行,因此还有最后一步将所有部分总计添加到最终的“总计”中。但是,因为多个并发任务(在不同线程上)可以竞争grandTotal变量,所以以线程安全的方式对其进行更改很重要。(我更改了MSDN变量的名称以使其更清楚)longgrandTotal=0;Parallel.ForEach(nums,//sourcecollection()=>0,//初始化局部变量的方法(j,loop,subtotal)=>//每次迭代循环调用的方法subtotal+nums[j],//valuetobepassedtonextiterationsubtotal//小计的最终值传递给localFinally函数参数(subtotal)=>Interlocked.Add(refgrandTotal,subtotal)在MS例子中,修改任务内部的参数小计身体是不好的做法,没有必要。即代码subtotal+=nums[j];返回小计;小计+=nums[j];返回小计;会更好,因为只returnsubtotal+nums[j];可以缩写为lambdashorthandprojection(j,loop,subtotal)=>subtotal+nums[j]通常,Parallel.For/Parallel.ForEach的localInit/body/localFinally重载允许运行每个任务的初始化和清理代码在(分别)任务执行taskBody迭代之前和之后。(注意Forrange/Enumerable传递给parallelFor/Foreach会被分成IEnumerablebatches,每批会分配一个Task)在每个task中,localInit会被调用一次,bodycode会被反复调用,每批item是调用一次(0..N次),完成后会调用一次localFinally。此外,您可以通过localInitFunc的通用TLocal返回值传递任务持续期间所需的任何状态(即传递给taskBody和localFinally委托)——我在下面将此变量称为taskLocals。“localInit”的常见用法:“localFinally”动作的常见用法:taskBody是循环操作的紧密部分-您需要对其进行优化以提高性能。这是用评论的例子总结的最好的总结:publicvoidMyParallelizedMethod(){//共享变量。不是线程安全的varitemCount=0;Parallel.For(myEnumerable,//localInit-每个任务调用一次。()=>{//本地`task`变量没有争用//因为每个任务永远不能由多个线程同时运行varsqlConnection=newSqlConnection("connstring...");sqlConnection.Open();//这是我们希望在任务持续期间保持的`tasklocal`状态returnnew{Conn=sqlConnection,RunningTotal=0}},//TaskBody.分配给此任务的批次中的每个项目调用一次(item,loopState,taskLocals)=>{//...在我们任务的独立连接上使用(varcommand=taskLocals.Conn.CreateCommand())在这里做一些花哨的Sql工作using(varreader=command.ExecuteReader(...)){if(reader.Read()){//没有争用`taskLocal`taskLocals.RunningTotal+=Convert.ToInt32(reader["countOfItems"]);}}//与我们的`taskLocal`参数m类型相同},//LocalFinally在body完成后为每个Task调用一次//AlsotakesthetaskLocal(taskLocals)=>{//对我们的TaskLocals的任何清理工作(就像你在`finally`范围内所做的那样)if(taskLocals.Conn!=null)taskLocals.Conn.Dispose();//做任何减少/聚合/同步工作。//注意:这里有争论!Interlocked.Add(refitemCount,taskLocals.RunningTotal);以及更多示例:每个任务的无竞争力字典示例每个任务的数据库连接示例作为@HonzaBrestan答案的扩展Parallelforeach如何将工作拆分为任务也很重要,它将多个循环迭代分组到一个任务中,因此实际上localInit()将在循环的每n次迭代中被调用,并且可以同时启动多个组。localInit和localFinally是为了确保并行的foreach循环可以将每次迭代的结果组合成一个结果,而无需在循环体中指定lock语句。为此,必须提供所需值的初始化创建(localInit),然后可以在每个主体迭代中处理本地值,然后提供一种方法以线程安全的方式组合每个组(localFinally)的值.如果你不需要localInit来同步任务,你可以使用lambda方法从周围的上下文中正常引用值,没有任何问题。有关使用localInit/Finally和向下滚动以使用局部值进行优化的更深入的教程,请参阅C#中的线程(Parallel.For和Parallel.ForEach),JosephAlbahari实际上是我所有线程的goto资源。您可以获得MSDN关于正确的Parallel.ForEach重载的提示。localInit委托为参与循环执行的每个线程调用一次,并返回每个任务的初始本地状态。这些初始状态将传递给每个任务的第一个文字调用。然后,主体的每个后续调用都会返回一个可能已修改的状态值,该值将传递给主体的下一次调用。在您的示例中,()=>0是一个仅返回0的委托,因此该值用于每个任务的第一次迭代。我这边稍微简单一点的例子以上是C#学习教程:ParallelForEach的本地初始化是如何工作的?如果分享的所有内容对你有用,需要了解更多C#学习教程,希望你多多关注—classProgram{classPerson{publicintId{get;放;}公共字符串名称{得到;放;}publicintAge{得到;放;}}staticListGetPerson()=>newList(){newPerson(){Id=0,Name="Artur",Age=26},newPerson(){Id=1,Name="Edward",Age=30},newPerson(){Id=2,Name="Krzysiek",Age=67},newPerson(){Id=3,Name="Piotr",Age=23},newPerson(){Id=4,姓名="亚当",年龄=11},};staticvoidMain(string[]args){列出人员=GetPerson();intageTotal=0;Parallel.ForEach(persons,()=>0,(person,loopState,subtotal)=>subtotal+person.Age,(subtotal)=>Interlocked.Add(refageTotal,subtotal));Console.WriteLine($"总年龄:{ageTotal}");控制台.ReadKey();}}本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: