【问题标题】:OutOfMemoryException When Creating a Large Bitmap in CF.NET在 CF.NET 中创建大位图时出现 OutOfMemoryException
【发布时间】:2010-09-22 19:11:03
【问题描述】:

我的紧凑框架应用程序通过将所有项目渲染到一个大的位图表面来创建一个平滑滚动列表,然后将该位图复制到屏幕上的偏移位置,以便仅显示适当的项目。旧版本只渲染当时应该出现在屏幕上的项目,但是这种方法对于平滑的滚动界面来说太慢了。

最初创建大位图时,它偶尔会生成 OutOfMemoryException。如果用户对设备执行软重置并再次运行应用程序,则可以毫无问题地执行创建。

看起来这个位图不是在程序内存中生成的,因为应用程序使用的程序内存量与新平滑滚动方法之前使用的程序内存量大致相同。

有什么方法可以防止这个异常吗?有什么方法可以在抛出异常之前释放我需要的内存(无论它在哪里)?

【问题讨论】:

    标签: compact-framework gdi+ bitmap


    【解决方案1】:

    您的位图肯定正在在程序内存中创建。位图需要多少内存取决于它有多大,而这个所需大小是否会产生 OutOfMemoryException 取决于 PDA 有多少可用内存(这使得这是一个随机发生的错误)。

    抱歉,这通常是一种不可取的控件渲染技术(尤其是在 Compact Framework 上),除了增加 PDA 上的物理内存外,没有任何解决办法,这通常是不可能的(而且通常无法解决)无论如何,这个问题,因为无论设备有多少可用,CF 进程都被限制为 32MB)。

    您最好的选择是回到旧版本并提高其渲染速度。 CF 上还有一种简单的技术可以使控件双缓冲以消除闪烁。

    【讨论】:

    • 你确定它是在程序内存中创建的吗?我有 GC.GetTotalMemory(false) 输出在位图创建之前和之后进行调试,并获得以下信息:位图大小:320x12450 之前的内存:211676 之后的内存:211996 看起来非常小。
    • 320x12450 = 400 万像素。在 16bpp 时,单独保存该图像需要 8MB 的内存。您想知道为什么会收到 OOM?
    • 我意识到 GC 并不总是记录内存使用情况,因为位图可能包含非托管内存。所以我使用 GlobalMemoryStatus() 来检查可用的物理内存。在 VGA 设备上,这给了我一个 640x8650 的位图,损失了 1.3 兆字节的可用物理内存。
    • 在 PDA 上,除了程序内存(没有交换文件)之外,没有任何地方可以创建位图。
    • 杰克,听起来更像。但是你的位图比我想象的要大得多。
    【解决方案2】:

    我一发布就想到了您可以做些什么来解决新版本的问题。您遇到的问题是 CF 试图找到一块可用于巨大位图的连续内存块,这偶尔会成为问题。

    您可以创建一个较小的位图集合,而不是创建一个大位图,每个项目一个,并将每个项目渲染到它自己的小位图上。在显示期间,您只需复制所需的位图。 CF 创建一堆小位图比创建一个大位图要容易得多,而且你不应该有任何内存问题,除非这是一大堆项目。

    我应该避免像“没有解决办法”这样的表达方式。

    还有一点很重要:确保在完成后对每个位图调用 Dispose()。

    【讨论】:

    • 不是垃圾回收内存压缩的一部分吗?将 100 个单独的位图对象超过一个对象会产生更多的内存开销吗?它仍然比我有的任何其他想法都要好,所以我可能会在接下来的几天内尝试一下。非常感谢您的反馈。
    • GC 不能保证在任何特定时间收集,因此有时您可能有足够的内存来创建位图但 GC 尚未收集它 - OutOfMemoryException。
    • 与绘图表面需求相比,.NET 中的每个对象的内存开销非常微不足道,即使在 CF 中也是如此,因此无需担心。我在 CF 中对位图做了很多工作,创建一个巨大的位图总是很麻烦。
    • 要记住的另一件事:OutOfMemoryException 并不一定意味着您的进程确实内存不足,只是这就是引发的异常类型。我怀疑在这种情况下,CF 有时只是决定位图会占用太多可用内存并拒绝。
    【解决方案3】:

    由于您似乎遇到了设备限制,限制了您可以创建的位图空间的总大小(这些显然是在视频 RAM 中而不是一般程序内存中创建的),一种替代方法是替换大位图对象此处与一块普通的 Windows 内存块一起使用,通过 PInvoking BitBlt API 函数对其进行读取和写入。

    最初创建内存块很棘手,您可能想问另一个关于此的 SO 问题(GCHandle.Alloc 可用于创建“固定”对象,这意味着 .NET 不允许移动它在内存中,这在这里很重要)。我知道该怎么做,但我不确定我做的是否正确,我宁愿听取专家的意见。

    一旦您创建了大块,您将遍历您的项目,将每个项目渲染为一个您不断重复使用的小位图(使用您现有的 .NET 代码),然后将其 BitBlt 到您的适当位置内存块。

    创建整个缓存后,您的渲染代码应该像以前一样工作,不同之处在于您不是从大位图复制到渲染表面,而是从缓存块中进行 BitBlt。 BitBlt 的参数与 DrawImage 的参数基本相同(目标、源、坐标和大小等)。

    由于您是以这种方式使用常规内存而不是专门的视频 RAM 创建缓存,因此我认为您不会遇到同样的问题。但是,我肯定会先让块创建代码工作并进行测试,以确保它每次都能创建足够大的块。

    更新:实际上,理想的方法是拥有一组较小的内存块而不是一个大的内存块(就像我认为的位图方法的问题一样),但您已经有足够的工作要做了。我使用过处理 5MB 和 10MB 对象的 CF 应用程序,无论如何这不是一个大问题(尽管当该块被固定时它可能是一个更大的问题 - 我不知道)。顺便说一句,我一直对创建位图的 OOME 感到惊讶,因为我知道位图比可用内存小得多,你也是 - 现在我知道为什么了。抱歉,我一开始以为这很容易解决。

    【讨论】:

    • 如果(!)这种方法有效,您甚至可以将其封装为与 BitMap 具有相同接口的 BigBitMap,然后将其放入现有代码中。
    • 呃,也许不是。我忘了 BitMap 实现了 Image。
    • 知道如何确定我需要为位图分配多少内存吗?
    • 没关系 - 这也行不通。我只是做了一些基本测试,试图一次创建大约 4MB 的东西,而 .NET CF 就是做不到,即使是 10 个 400,000 长度的字节数组。
    • 不过,我有另一个想法:96x96 图像从何而来?它们是以 JPEG 格式存储为文件还是作为资源嵌入,还是在缓存时自定义绘制?
    【解决方案4】:

    我建议回到只渲染部分数据的旧机制,因为完全渲染数据的大小显然是一个问题。为了帮助防止出现渲染问题,我可能会在当前视图的上方和下方预渲染几行,这样它们就可以在有限的影响下“滚动”进来。

    【讨论】:

    • 他真正的问题是 96x96 的图像,这些图像要么必须在磁盘上(这意味着慢),要么在显存中(这意味着 OOME)。
    • 我最终做了这样的事情。我测试机器以确定我可以安全地放入内存中的项目数量,然后将这些项目渲染到可以用作快速滚动缓冲区的“窗口”位图。该窗口包含一些在当前列表之前和之后的一些。没那么好,但仍然很快。
    • 感谢您抽出宝贵时间回来告诉我们您做了什么以及效果如何。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-09
    • 2014-09-30
    • 2011-12-12
    • 1970-01-01
    • 2012-05-18
    • 2014-04-16
    • 2012-04-11
    相关资源
    最近更新 更多