使用任务处理大型输入文件

时间:2016-11-08 06:21:47

标签: .net vb.net task-parallel-library

2016年11月14日更新,因为我看到一些答案(非常感谢)正在询问更多细节......

语言:VB.Net / Framework: 4.5

亲爱的TPL专家,我是任务处理库(TPL)的新手,非常感谢以下任何帮助:

我需要处理一个大文件(几十万条记录)。每条记录都是一个制表符分隔的记录,最多包含4个字段。

对于每个读取的记录,我需要提取字段并调用asnyc任务来处理这些字段。

显然,我需要通过一次只允许处理最多10个记录来限制流程,也就是说,一次最多启动10个任务。

例如,如果我读取了第一个10条记录,那么将启动10个任务。如果任何任务之一完成,我会读取下一条记录并启动另一项任务,依此类推,直到任务结束。基本上,我想优化我可以同时阅读和处理的记录数量。

到目前为止,这是我提出的:

   rec_list = New StreamReaderEnumerable( file_spec )

   For each rec In rec_list 

      task_list.Add( Task.Run( Async Function()
                                   Return Await task_func( rec )
                               End Function
                              )
                   )

      If ( task_list.Count >= 10 ) Then

         Task.WhenAll( task_list )

         task_list.Clear()

      End If

   Next

“StreamReaderEnumerable”类用于一次从文件中返回一条记录(作为Enumerable源),然后我将一个Task添加到一个列表中,以便一次处理10个任务。

没有必要维护任何类型的订单,因为每个任务可以随时完成 - 我只是想提高一次处理大于一个记录的效率。

问题是,我正在等待所有10个任务完成才能继续。最好进一步优化它。

我想我正在寻找的是ForEachAsync类型的枚举器 - 但还没有找到任何明确的例子......

任何建议都将受到赞赏。

1 个答案:

答案 0 :(得分:1)

  1. 您应该查看BlockingCollection class。您可以通过10限制集合的大小,创建一个消费者任务,它将为集合添加一行,以及一些生产者任务,这些任务将从集合中占用一行直到文件末尾。像这样:

    var numbers = new BlockingCollection<string>(10);
    // this task should be refactored to accept a method
    Task.Run(() =>
    {
        while (!lines.IsCompleted)
        {
            try
            {
                var line = lines.Take();
                // do stuff here
            }
            catch (InvalidOperationException)
            {
                Console.WriteLine("Adding was completed!");
                break;
            }
        }
        Console.WriteLine("No more lines to take.");
    });
    
    // A simple blocking producer with no cancellation.
    Task.Run(() =>
    {
        while (!END_OF_FILE_CHECK)
        {
            // this method will block until you have less than 10 lines in a collection
            lines.Add(line);
        }
        lines.CompleteAdding();
    });
    

    这两个任务都应该使用LongRunning flag创建,因为这将为所有这些任务提供一个线程:

      

    指定任务将是一个长时间运行的粗粒度操作,涉及比细粒度系统更少,更大的组件。它提供了TaskScheduler提示可能需要超额认购的提示。 Oversubscription允许您创建比可用硬件线程数更多的线程。它还向任务调度程序提供了一个提示,即任务可能需要一个额外的线程,这样它就不会阻止本地线程池队列中其他线程或工作项的前进。

  2. 正如@ScottChamberlain建议的那样,其他方法是使用具有有限并行度的TPL Dataflow的相同生产者/消费者模式,如下所示:

    var workerBlock = new ActionBlock<string>(
        line =>
        {
            // do stuff here   
        },
        // No more than 10 lines simultaneosly
        new ExecutionDataflowBlockOptions
        {
            MaxDegreeOfParallelism = 10
        });
    
    while (!END_OF_FILE_CHECK)
    {
        workerBlock.Post(line);
    }
    workerBlock.Complete();
    
    // Wait for all messages to propagate through the network.
    workerBlock.Completion.Wait(); 
    

    您还可以引入一些生产者块like here in tutorial,并添加一些与worker相关联的其他块来处理它的结果。