读取巨大的xml元素值后清理内存

时间:2013-04-18 15:44:36

标签: c# .net garbage-collection xmltextreader

我很少转到这里寻求帮助,但这让我发疯:我正在读一个包含任意数量项目的xml文件,每个项目都有一个b64编码的文件(以及一些附带的元数据)。最初我只是将整个文件读成XmlDocument,但是虽然代码更清晰,但我意识到文件的大小没有限制,而XmlDocument占用了大量内存并且可能耗尽如果文件足够大。所以我重新编写了代码,而不是使用XmlTextReader,如果问题是程序被发送到一个包含大量合理大小的附件的xml文件,那么效果很好......但是仍然存在一个大问题,那就是在哪里,我转向你:

如果我的xml阅读器位于File元素,该元素包含一个巨大的值(比如500MB),我调用reader.ReadElementContentAsString(),我现在有一个占用500MB的字符串(或者可能是OutOfMemoryException)。在任何一种情况下,我想做的只是写入日志,“文件附件完全太大,我们将忽略它并继续前进”,然后转到下一个文件。但是看起来我刚刚尝试读取的字符串似乎没有被垃圾收集,所以实际发生的是字符串占用了所有RAM,并且它之后尝试读取的每个其他文件也会抛出OutOfMemoryException,即使大多数的文件将非常小。

回想一下:此时,我正在将元素的值读入本地字符串,因此我希望期望它有资格立即进行垃圾收集 (和因此,当程序试图读取下一个项目并发现它没有可用内存时,它最终将被垃圾收集。但我已经尝试了一切,以防万一:将字符串设置为null,调用显式GC.Collect() ...没有骰子,任务管理器指示GC仅收集了大约40k,其中只需要存储的约500k字符串输入,我仍然会尝试读取其他内容的内存异常。

似乎没有任何方法可以在不读取该元素的情况下使用XmlTextReader知道xml元素中包含的值的长度,所以我想我已经卡住了读取字符串...我是缺少某些东西,或者是否真的没有办法从xml文件中读取一个巨大的值而没有完全破坏你的程序之后做任何事情的能力?我对此感到疯狂。

我已经阅读了一些关于C#的GC和LOH的内容,但我读过的内容并没有告诉我这会发生......

如果您需要任何进一步的信息,请与我们联系,谢谢!

编辑:我确实意识到这个过程是作为一个32位进程运行的,这意味着它比内存需要更多的内存。修复了这不再是问题,但它仍然是我想解决的行为。 (它需要更多和/或更大的文件才能达到抛出OutOfMemoryException的程度,但是一旦抛出它,我仍然无法及时回收那些内存。)

3 个答案:

答案 0 :(得分:1)

我有一个类似的问题,用于将大文件作为base64字符串传输的soap服务。

我当时使用的是XDocument而不是XmlDocument,这对我有用。

答案 1 :(得分:1)

您可以使用XmlReader.ReadValueChunk方法一次读取一个“块”元素的内容,而不是一次尝试读取整个内容。这样,您可以在某种程度上决定数据太大,然后忽略它并记录事件。 StringBuilder可能是将收集的char数组块组合在一个字符串中的最佳方法。

如果要使用GC.Collect()释放内存,可以使用GC.WaitForPendingFinalizers()强制立即终结和释放内存。这可能会影响性能(甚至挂起,请参阅链接后面的描述),但是你应该摆脱大型对象,假设你不再有任何对它们的实时引用(即局部变量已经超出范围或它们的值设置为null)并正常继续操作。你当然应该把它作为最后的手段,当内存消耗是一个问题,你真的想要强制摆脱多余的内存分配。

我已经在内存敏感的环境中成功使用了GC.Collect();GC.WaitForPendingFinalizers();组合,以便将应用程序的内存占用量保持在100MB以下,即使它读取了一些非常大的XML文件(> 100MB)。为了提高性能,我还使用Process.PrivateMemorySize64来跟踪内存消耗并仅在达到某个限制后强制终止。在我改进之前,内存消耗有时会超过1GB!

答案 2 :(得分:0)

我不是肯定的情况,但我认为你需要处理XmlTextReader。将超大节点后的节点的xmlpath保存到字符串,将海量字符串设置为null,然后处理XmlTextReader并在大节点后的节点重新打开它。根据我的理解,如果将字符串设置为null,或者它超出范围,GC应尽快释放该内存。我似乎更有可能释放字符串,但你继续使用XmlTextReader进行操作,而unsafe现在占用了大量内存。

想到的另一个想法是尝试在{{1}}块中执行此操作然后明确释放内存,但是,它看起来不可能(其他人可能知道但是在环顾四周之后)似乎不安全的块仍然是GC'd,它只是给你指针)。另一种选择,虽然非常可怕,但是可以用C或C ++编写一个用于解析的dll,并从你的C#项目中调用它。

在做最后一次疯狂的事情之前尝试第一个建议:)