【问题标题】:How to detect if loading an image will throw an OutOfMemory exception in .NET?如何检测加载图像是否会在 .NET 中引发 OutOfMemory 异常?
【发布时间】:2011-03-13 00:48:24
【问题描述】:

我有一个使用 .NET 3.5 SP1 编写的应用程序,它从外部站点下载图像并将它们显示给最终用户。在极少数情况下,我的用户会遇到 OutOfMemory 错误,因为他们正在下载大量图像。有时与这些图像相关的原始数据很大,但更多时候,图像的尺寸很大。我意识到我可能永远无法解决这些 OOM 错误是针对特定图像引发的事实。但是,如果我能在尝试加载图像之前以某种方式确定加载特定图像是否会导致 OOM 问题,那将非常有帮助。

图像的数据被加载到一个 Stream 中,然后通过调用 System.Drawing.Image.FromStream(stream) 将图像本身转换为 System.Drawing.Image。我没有先将这些图像存储在磁盘上的选项。它们必须通过内存加载。

如果有人有任何提示或建议可以让我检测到加载图像会导致 OOM 异常,我将非常感激。

【问题讨论】:

  • “图像的数据被加载到流中”... 是魔法,还是如何?
  • @Will - “从外部站点下载图像”
  • @Joel 所以他得到一个流,然后将该流馈送到内存流中,然后将该流与图像一起使用?他想知道为什么他的内存不足?为什么不直接使用原始流并去掉中间人?我是疯子吗?
  • @Will - 这太疯狂了,但基思没有说“内存流”。我的印象是他将来自 HTTP 请求的流直接提供给 Image.FromStream 方法。
  • @Joel 我知道,但在这些地方,像这样疯狂的事情是正常的。他所说的“将图像的数据加载到流中”的方式是一种奇怪的说法,即“我通过 HTTP 响应流获取图像”。

标签: .net gdi out-of-memory


【解决方案1】:

您可以使用MemoryFailPoint 类来检查内存可用性。

最好的

【讨论】:

  • 请大家阅读它是如何工作的,它是目前为止以“try/catch”方式处理大量内存分配的最佳方式。
  • 我会调查的。快速浏览一下让我觉得这可能不是我所需要的,因为文档说我需要指定“操作预期使用的内存兆字节数”。问题是我不知道 GDI+ 在使用 System.Drawing.Image.FromStream 加载图像时会使用多少内存。
【解决方案2】:

OutOfMemory 是您没有很多好的选择的例外情况之一。任何可以最终预测您将获得异常的东西都可能只需要生成异常。

我想说,最好的办法是分析行为并创建自己的预测规则集,或者只是将最大尺寸硬编码到您的应用程序中。它不漂亮,但它会让你到达那里。

【讨论】:

    【解决方案3】:

    你可以看看这个问题,看看它是否有帮助: How do I reliably get an image dimensions in .NET without loading the image?

    我们的想法是只下载整个图像的一部分(特别是标题),以便您可以读取元数据。然后您可以使用那里的信息来确定图像有多大,如果您发现它太大,则拒绝完全下载。

    不利的一面是,您似乎必须编写一个方法来分解您希望能够处理的每种文件类型的二进制文件。

    【讨论】:

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

    你遇到了先有鸡还是先有蛋的问题。要进行某种猜测,您需要知道图像的大小。在加载之前您不知道大小。

    无论如何,它并没有真正的帮助。你是否得到 OOM 真的取决于虚拟内存地址空间的碎片化程度。这在 Windows 中不容易找到。 HeapWalk() API 函数是必需的,这是一个不健康的函数。查看 MSDN 库文章中的小字。在托管程序中尤其糟糕,不要使用它。

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

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

    【讨论】:

      【解决方案5】:

      如果您从外部站点下载图像,并且外部站点设置了Content-Length HTTP 标头,您甚至可以在开始下载流之前估计图像是否适合内存。 ..

      【讨论】:

      • 我们确实得到了 content-length 标头。但是,数据的绝对物理大小并不能很好地预测加载文件将使用多少内存。我见过小于 5MB 的文件在加载时会超过 800MB,因为它们的尺寸太大了。
      【解决方案6】:

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

      【讨论】:

      • 我们在下载这些图片时确实使用了这种技术。但是,我很确定 System.Drawing.Image.FromImage 使用了它自己的内部缓冲区,这无论如何都会导致堆碎片。
      • 你是在打电话给 GDI 的所有处理器吗?我以前被烧毁过,几乎GDI+库中的每个小类都需要一个dispose调用来释放非托管内存。可能是因为这个导致内存泄露,导致OOM?
      • 我确信我们正在正确处理所有对象。我们的用户可以花费数小时查看正常大小的图像,并且内存占用保持稳定。只是偶尔出现的具有巨大尺寸的图像会吞噬所有剩余的内存。
      猜你喜欢
      • 2015-01-27
      • 2014-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-20
      • 1970-01-01
      • 2011-09-03
      相关资源
      最近更新 更多