【问题标题】:Parallel Looping, Iterate through large XML files causes out of memory并行循环,遍历大型 XML 文件导致内存不足
【发布时间】:2019-12-30 16:11:28
【问题描述】:

我有一个需要解析的文件列表(大小可以在 3KB 到 500MB 以上)。

为了更快地完成,我想使用 Parallel.ForEach 指令来迭代我的文件列表。

我知道我可以使用:

Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 2 }, file =>
{
    //Do stuff
});

为了确保同时只处理两个文件。 但是,在这两个文件超过 500MB 的情况下,我遇到了内存不足异常。

您知道 C# 中是否有一种方法可以使用布尔值来限制 ParallelOptions。 理想情况下,我希望在处理的文件总大小低于 1GB 时处理尽可能多的文件(或等到处理完的文件)

我还在考虑对我的文件列表进行排序(按大小),并在并行 foreach 循环中将第一个与最后一个放在一起(假设总大小低于 1GB)。但再一次,我不确定:

  1. 如果可能的话
  2. 语法是什么

据我了解,Parallel.ForEach 正在按照给定顺序遍历列表(在这种情况下,无法指定如何遍历我的列表...)

感谢您提供任何有关您将如何做的建议。


编辑1:

这是我如何读取文件的代码:

我需要从特定节点开始读取:“RootElt” - 这就是我不使用 File.ReadAllText() 的原因

using (XmlReader reader = XmlReader.Create(fi.FullName))
{
    reader.ReadToDescendant("RootElt");
    return reader.ReadOuterXml();
}
return string.Empty;

注意: 我最初使用 XDocument 并简单地执行: doc.Load() 但这会导致内存不足异常(即使我一个一个地处理文件) - 这不是使用XmlReader解决方案的案例

读完后,我调用我的反序列化方法:

private T Deserialize<T>(string xml)
{
    using (TextReader reader = new StringReader(xml))
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        var report = serializer.Deserialize(reader);
        return (T)report;
    }
 }

【问题讨论】:

  • 您是如何解析文件的?
  • 我们需要minimal reproducible example。你在做什么会产生内存不足的异常?如果您的代码泄漏内存,则需要解决。
  • 已知XmlReaderXmlSerializer 类在滥用时会泄漏内存。请与我们分享一些代码。您能否确认您的应用程序以 64 位运行?
  • 当您创建XmlSerializer 时,它会为传入的类型创建一个缓存程序集(如果还没有)。如果您在多线程环境中执行此操作,则很容易生成同一动态程序集的多个副本,这些副本不是垃圾收集。我强烈怀疑这是您的内存泄漏的根源。
  • 在您创建序列化程序的地方简单地添加一个lock 可能就足够了。这应该足以确保您不会创建动态序列化程序集的多个副本。但是,在我看来,I/O 代码并不是真正可并行化的。 Task Parallel Library: Dataflow 你可能会做得更好

标签: c# parallel-processing


【解决方案1】:

您可以使用以下多线程构造:

public class FileProcessor
{
    private const long TotalSizeMax = 1073741824; // 1 GB
    private static long _totalSizeCurrent;

    public void ProcFiles(IList<FileInfo> fiList)
    {
        var totalFiles = fiList.Count;
        var index = 0;
        while (totalFiles > index)
        {
            var fi = fiList[index];
            Monitor.Enter(_totalSizeCurrent);
            var totalCandidate = _totalSizeCurrent + fi.Length;
            if (totalCandidate > TotalSizeMax)
            {
                Monitor.Exit(_totalSizeCurrent);
                Task.Delay(2000).Wait(); // delay 2 seconds
                continue;
            }
            _totalSizeCurrent = totalCandidate;
            Monitor.Exit(_totalSizeCurrent);
            Task.Run(() =>
            {
                // Start parse FileInfo fi
                //...

                // End parse
                Monitor.Enter(_totalSizeCurrent);
                _totalSizeCurrent -= fi.Length;
                Monitor.Exit(_totalSizeCurrent);
            });

            index++;
        }
    }
}

【讨论】:

  • 这就是(我猜)我所要求的。我设法通过仅读取感兴趣的元素和标签来绕过我的问题(过滤内容而不是读取 XML 中的所有内容......) - 我将批准这个问题,因为它回答了我最初的问题。谢谢
猜你喜欢
  • 1970-01-01
  • 2016-01-01
  • 2017-01-13
  • 1970-01-01
  • 2013-11-24
  • 2013-10-24
  • 1970-01-01
  • 2015-08-26
  • 2011-04-10
相关资源
最近更新 更多