TPL Parallel.Foreach具有IO和计算密集型任务

时间:2016-04-27 06:48:06

标签: c# task-parallel-library parallel.foreach

我在Azure blob存储上有数十亿个xml日志文件,需要进行处理,查询和结果存储。我正在使用Parallel.Foreach,因为文件处理是相互独立的。

Parallel.ForEach<String> (listOfFeatureFiles, file => { 
  //For each file that was created
  string fileName = file;
  string directoryPath = outputfolderPath + "/" + FeatureFolderName;
  string finalFilePath = directoryPath + "/" + fileName;

  DownloadContent();
  XMLParseAndQueryData();
  UploadResultToQueue();
  DeleteLocalCopy();
});

如果它只是计算密集型,那么我可能拥有最大的CPU使用率,但是在我的场景中,20%的文件比其余80%的文件大得多(以GB为单位)。这通常导致4个内核的CPU使用率仅为50%。如何优化它以实现最大CPU使用率,即&gt; 90%?

我的假设是,一旦任务下载大文件,就不会使用cpu,但同时也不会创建新线程,这可以利用处理能力。我对这个假设可能是错的,并且会理解与其否定的具体联系。

2 个答案:

答案 0 :(得分:1)

我为我的一个客户构建了一个类似的应用程序,它也处理了大量不同大小的xml文件。下载会干扰CPU的使用,你无能为力。但是,您可以通过使用具有多个使用者的BlockingCollection来优化CPU使用率,并在下载较大文件时始终保持处理较小的文件。

答案 1 :(得分:1)

  

我的假设是,一旦任务下载大文件,就不会使用cpu,但同时也不会创建新的线程,这可以利用处理能力。

您确定您有足够的网络带宽吗?下载文件实际上并不是此过程的瓶颈吗?

如果你是,并且缓慢添加线程实际上是减慢你的速度,那么快速而肮脏的解决方案是强制ThreadPool(内部由Parallel.ForEach()使用)来有更多的线程。您可以致电ThreadPool.SetMinThreads

正确的解决方案是使IO绑定方法异步,并独立于CPU绑定方法安排它们。为了帮助安排,您可以使用TPL数据流(EnsureOrdered需要预发布版本):

var cpuBoundOptions = new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = Environment.ProcessorCount,
    EnsureOrdered = false
};

var ioBoundOptions = new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism = 10, // TODO: tweak this value as necessary
    EnsureOrdered = false
};

var downloadBlock = new TransformBlock<string, string>(async file =>
{
    await DownloadContentAsync(file);
    return file;
}, ioBoundOptions);

var parseBlock = new TransformBlock<string, string>(file =>
{
    XMLParseAndQueryData(file);
    return file;
}, cpuBoundOptions);

var uploadBlock = new TransformBlock<string, string>(async file =>
{
    await UploadResultToQueue(file);
    return file;
}, ioBoundOptions);

var deleteBlock = new ActionBlock<string>(file => DeleteLocalCopy(file));

var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };

downloadBlock.LinkTo(parseBlock, linkOptions);
parseBlock.LinkTo(uploadBlock, linkOptions);
uploadBlock.LinkTo(deleteBlock, linkOptions);

foreach (var file in listOfFeatureFiles)
{
    downloadBlock.Post(file);
}

downloadBlock.Complete();
await deleteBlock.Completion;