每个孩子运行一个单独的任务

时间:2018-07-23 07:20:33

标签: c# task-parallel-library

我有一个树数据结构,每个兄弟姐妹可以并行处理。

当前,我创建一个单独的任务来处理每个孩子。这是幼稚的做法,一旦树具有给定的大小/深度,会损害性能吗?

或者CLR是否设计为处理任意任务,因为它们未绑定到特定的OS线程?!

2 个答案:

答案 0 :(得分:1)

这个问题的最大答案是肯定的。但是,为了完全理解它,您将不得不更深入地研究任务,看看您的任务是long-running tasks还是short running

第二个链接包含您问题的答案,但为您总结一下:

默认情况下,任务是短期运行的。 这是什么意思

该任务将在线程池内创建。如果您要执行的任务要花费大量时间,这意味着长时间运行-您应避免在线程池中创建任务,因为它将使您的线程池中充满越来越多的任务最终会溢出,将其称为,即线程池。

长时间运行意味着任务将在线程池之外的单个线程上创建,可以使用TaskCreationOptions Enumeration进行定义:

TaskCreationOptions.LongRunning

答案 1 :(得分:1)

当问一个问题时,“为什么”与什么以及如何一样重要。从(丢失的)评论中:

  

好吧,这是AST,因此,如果我们有一堆文件,则每个文件可以有100多个子树。每个节点的实际处理并不复杂,但是数量可能在100个并发任务中。

这应该是问题的一部分,并说明所需的处理过程。

询问子任务就像询问要在解析器规则中嵌入哪些动作:为什么要完全将动作嵌入解析器规则?那只是一种技术。是否合适取决于您在做什么。使用AST的访问者可能会更好。或对特定规则做出反应的监听器。例如,ANTLR提供了所有三个选项。您选择哪一个取决于工作

就像解析一样,作业的类型在并行计算中也很重要。

当您拥有大量数据时,创建比核心更多的任务只会浪费时间。最好创建更少的任务并在其中拆分数据。这样,单个任务就可以处理所有分配的数据,而无需线程切换和所涉及的延迟。

这称为数据并行性。 TPL通过Parallel类和Parallel LINQ支持它。如果您的IEnumerable包含需要处理的数据,则可以使用以下命令并行处理它们:

Parallel.ForEach(myCollection,singleItem=>SomePrecessing(singleItem));

Parallel.ForEach将创建(大约)与核心一样多的任务,对数据进行分区并将每个部分发送到核心。

并行LINQ允许您只需添加一个.AsParallel()调用即可并行执行LINQ查询的每个运算符,例如:

var results = from file in listOfFiles.AsParallel()
              from stock in ParseFileToGetStocks(file)
              where stock.Price >100
              group stock by stock.Category into g
              select new {category=g.Key,Max=g.Max()....}

解析,过滤,分组和聚合部分将在单独的并行步骤中运行。

只要您可以从树中创建IEnumerable,例如使用迭代器,就可以将其与Parallel.For / ForEach或PLINQ一起使用。

不过,这只是一个选项,可能不适用于此问题。毕竟,它的一部分是读取大量文件,这是IO操作。为什么不像单独的命令的Powershell或bash管道那样,按分开步骤读取和处理文件?

这是一个数据流问题,受TPL Dataflow库支持。您可以将工作分解为单独的块,每个块都在自己的任务上运行。一个块可以加载和解析文件,第二个块可以从外部服务请求一些数据,最后一个块可以处理结果。

比方说,这些文件仅包含一些库存数据,您必须从外部服务请求更多数据。第一块可以解析文件并提取股票,第二块可以请求其他数据,最后一步可以处理所有数据。创建数据流将允许所有作业并行运行。可能需要大量CPU的处理步骤处理另一个文件的结果,同时进行网络绑定下载,例如:

var parserBlock=new TranformManyBlock<string,StockData>(file =>{
                {
                    var stock=Parse(file);
                    foreach(var stock in stocks)
                    {
                        yield return new StockData(...);
                    }
                });
var downloader=new TransformBlock<StockData,CompleteData>(stock =>
               {
                   var extraData=someService.Get(stock.Symbol, stock.Date....);
                   return new CompleteData(stock,extraData);
               });

 var calculateBlock= new ActionBlock<CompleteData>(stock=>
               {
                   var results=HeavyProcessing(stock);
                   WriteResults(stock,results);
               });

var linkOptions=new DataflowLinkOptions{PropagateCompletion=true";
parserBlock.LinkTo(downloader,linkOptions);
downloader.LinkTo(calculateBlock,linkOptions);

一旦有了块管道,就可以开始向其发布数据:

foreach(var node in tree)
{
    parserBlock.Post(node.File);
}

完成后,您将第一个块告诉Complete()并等待,直到最后一个块的所有块都完成了:

parserBlock.Complete();
await calculateBlock.Completion;

由于下载程序将仅等待该服务的响应,因此您可以指定例如最多同时运行5次下载:

var parallelOptions=new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=5};

var downloader=new TransformBlock<>(...,parallelOptions);