如何在.NET中检测加载图像是否会引发OutOfMemory异常?

5
我有一个使用 .NET 3.5 SP1 编写的应用程序,它从外部网站下载图像并向最终用户显示。偶尔会出现 OutOfMemory 错误,因为用户下载了巨大的图像。有时与这些图像相关联的原始数据很大,但更常见的是图像的尺寸巨大。我意识到可能永远无法避免这些特定图像引起的 OOM 错误。然而,如果我能在尝试加载图像之前确定加载特定图像是否会导致 OOM 问题,那将非常有帮助。
图像的数据被加载到 Stream 中,然后通过调用 System.Drawing.Image.FromStream(stream) 将图像本身转换为 System.Drawing.Image。我不能选择先将这些图像存储在磁盘上。它们必须通过内存加载。
如果有人有任何提示或建议,可以让我检测加载图像是否会导致 OOM 异常,我将非常感激。

图片的数据是通过魔法加载到流中的吗? - user1228
@Will - "从外部网站下载图像" - Joel Mueller
@Joel所做的是获取一个流,然后将该流输入到MemoryStream中,然后再使用该流与图像一起使用?他想知道为什么会出现内存不足的情况?为什么不直接使用原始流并切掉中间人?难道我是疯了吗? - user1228
@Will - 这将是相当疯狂的,但 Keith 没有说“memorystream”。我印象中他是直接将 HTTP 请求的流馈送到 Image.FromStream 方法中。 - Joel Mueller
@Joel 我知道,但这些地方的疯狂事情很正常。他说的方式,“图像数据被加载到流中”是一种奇怪的说法,“我通过HTTP响应流获取图像”。 - user1228
Will是正确的。对于混淆我感到很抱歉。我确实通过HTTP响应流获取了图像。 - Keith
6个回答

4

2
请大家阅读一下这个内容,这绝对是处理大内存分配的“try/catch”方式中最好的方法。 - Spence
我会调查一下。快速浏览让我认为这可能不是我需要的,因为文档说我需要指定“操作预计使用的内存大小(以兆字节为单位)”。问题在于,当使用System.Drawing.Image.FromStream加载图像时,我不知道GDI+将使用多少内存。 - Keith

1

OutOfMemory是其中一种异常情况,你没有很多好的选择。任何可以预测你会得到异常的结论都可能只是生成异常。

我会说,你最好的选择是通过分析行为并创建自己的预测规则集或直接在应用程序中硬编码最大尺寸。虽然不美观,但这样可以让你达到目的。


1

你可以看一下这个问题,看看它是否有帮助: 如何在 .NET 中可靠地获取图像尺寸而不加载图像?

那里的想法是仅下载总图像的一部分(特别是头文件),以便读取元数据。 然后,您可以使用那里的信息来确定图像的大小,并在看到它将太大时拒绝完全下载它。

不足之处在于,似乎您必须编写一个方法来分解每种要处理的文件类型的二进制文件。


是的,我们正在考虑使用这种方法。我们最大的担忧仍然是我们没有一个好的方法来准确预测System.Drawing.Image.FromImage将使用多少内存。我们需要支持.NET本地支持的任何图像类型。除了需要分解.NET支持的每个文件类型的二进制文件之外,我们还需要想出一种方法来准确预测加载这些图像时使用的内存量。这似乎是整个开发团队可能要花费数月时间才能完成的事情!:( - Keith

1

你遇到了一个鸡生蛋的问题。要猜测某种类型,你需要知道图像的大小。但在加载之前你并不知道它的大小。

即使知道了大小也没有什么帮助。是否会出现OOM错误实际上取决于虚拟内存地址空间的碎片程度。而这是在Windows中很难找到的。需要使用HeapWalk() API函数,但这是一个不健康的函数。请查看MSDN库文章中的细则。在托管程序中特别糟糕,不要使用它。

请注意,这个OOM异常与使用过多托管内存时得到的OOM异常不同。它实际上是一个GDI+异常,你可以轻松地从中恢复。只需捕获异常并显示“抱歉,无法完成”消息即可。

如果你确实知道前面的大小,那么你可以安全地假设width * height * 4 > 550 MB在32位程序中不起作用。这个限制在运行一段时间后会迅速下降。


1
如果您正在从外部网站下载图像,并且该外部网站设置了Content-Length HTTP头,则在开始下载流之前,您可能能够估计图像是否适合内存...

我们确实可以获取到content-length头信息。然而,数据的物理大小并不能很好地预测加载文件将使用多少内存。我曾经看到过一些小于5MB的文件,在加载时却会消耗超过800MB的内存,因为它们的尺寸非常大。 - Keith

1

我已经同意@Vagaus的回答,但我想补充一点,你应该只分配一次缓冲区并尝试重复使用它。如果你不断地分配和释放一个大缓冲区,你肯定会因堆上的碎片而遇到OOM问题。


我们在下载这些图片时确实会使用这种技术。然而,我非常确定 System.Drawing.Image.FromImage 会使用自己的内部缓冲区,这反而会导致堆碎片化。 - Keith
你是否在调用GDI相关的所有处理器?我之前就遇到过这种情况,GDI+库中的每个小类都需要调用dispose方法来释放非托管内存。也许你因为这个原因导致了内存泄漏,从而引起OOM错误? - Spence
我相信我们正在正确地处理所有对象。我们有用户可以花费数小时查看普通大小的图像,而内存占用保持稳定。只有偶尔会出现巨大尺寸的图像,会吞噬掉所有剩余的内存。 - Keith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接