如何使用Parallel.ForEach正确写入文件?

时间:2016-02-12 22:34:34

标签: c# parallel.foreach file-writing

我有一个任务,它逐行读取一个大文件,用它做一些逻辑,并返回一个我需要写入文件的字符串。输出的顺序无关紧要。但是,当我尝试下面的代码时,它会在读取我的文件的15-20k行后停止/变得非常慢。

public static Object FileLock = new Object();
...
Parallel.ForEach(System.IO.File.ReadLines(inputFile), (line, _, lineNumber) =>
{
    var output = MyComplexMethodReturnsAString(line);
    lock (FileLock)
    {
        using (var file = System.IO.File.AppendText(outputFile))
        {
            file.WriteLine(output);
        }
    }
});

为什么我的程序在运行一段时间后会变慢?有没有更正确的方法来执行此任务?

2 个答案:

答案 0 :(得分:8)

您基本上通过让所有线程都尝试写入文件来序列化您的查询。相反,您应该计算需要写入的内容,然后在结束时编写它们。

var processedLines = File.ReadLines(inputFile).AsParallel()
    .Select(l => MyComplexMethodReturnsAString(l));
File.AppendAllLines(outputFile, processedLines);

如果您需要清理数据,请打开一个流并启用自动刷新(或手动刷新):

var processedLines = File.ReadLines(inputFile).AsParallel()
    .Select(l => MyComplexMethodReturnsAString(l));
using (var output = File.AppendText(outputFile))
{
    output.AutoFlush = true;
    foreach (var processedLine in processedLines)
        output.WriteLine(processedLine);
}

答案 1 :(得分:5)

这与Parallel.ForEach内部负载均衡器的工作方式有关。当它发现您的线程花费大量时间阻塞时,它会通过在问题上抛出更多线程来加快速度,从而导致更高的并行开销,争用FileLock和整体性能下降。

为什么会这样?因为Parallel.ForEach不适用于IO工作。

你怎么解决这个问题?仅使用Parallel.ForEach进行CPU工作,并在并行循环外执行所有IO。

快速解决方法是通过使用接受Parallel.ForEach的重载来限制允许登记的ParallelOptions个线程数,如下所示:

Parallel.ForEach(
    System.IO.File.ReadLines(inputFile),
    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
    (line, _, lineNumber) =>
    {
        ...
    }