【问题标题】:C#, XNA, FileStream, Out of Memory ExceptionC#、XNA、FileStream、内存不足异常
【发布时间】:2011-12-17 07:50:13
【问题描述】:

我正在尝试将 XNA 游戏的每个渲染帧保存到硬盘。所以在每一帧中,我称之为代码:

        protected override void LoadContent()
        {
            ....
            colorRT = new RenderTarget2D(GraphicsDevice, backbufferWidth, backbufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth24);
            lightRT = new RenderTarget2D(GraphicsDevice, backbufferWidth, backbufferHeight, false, SurfaceFormat.Color, DepthFormat.None);
            specRT = new RenderTarget2D(GraphicsDevice, backbufferWidth, backbufferHeight, false, SurfaceFormat.Color, DepthFormat.None);

            ....
         }


        using (Stream stream = File.OpenWrite("color_"+frameNumber+".png"))
        {
            colorRT.SaveAsPng(stream, colorRT.Width, colorRT.Height);
            stream.Flush();
            stream.Close();
            stream.Dispose();
        }
        using (Stream stream = File.OpenWrite("light_" + frameNumber + ".png"))
        {
            lightRT.SaveAsPng(stream, lightRT.Width, lightRT.Height);
            stream.Flush();
            stream.Close();
            stream.Dispose();
        }
        using (Stream stream = File.OpenWrite("spec_" + frameNumber + ".png"))
        {
            specRT.SaveAsPng(stream, specRT.Width, specRT.Height);
            stream.Flush();
            stream.Close();
            stream.Dispose();
        }
        System.GC.Collect();
        frameNumber++;

但是如果此代码打开,内存消耗会不断增加,半分钟左右后我会收到 outOfMemory 异常。我添加了 flush、close、dispose 和 GC 调用来尝试解决问题,但这并没有改变任何事情。

谁知道我做错了什么?

【问题讨论】:

  • 顺便说一句,using 语句会为您调用 Dispose,如果您进行 dispose,则不需要关闭或刷新。问题可能与您一直保存 PNG 的事实有关?或者xxRT 变量一直在增长。文件流每次都被正确删除。
  • 是的,我也是这么想的,但我不明白这段代码是如何导致 outOfMemory 异常的。如果我把它注释掉,一切都运行良好
  • 您不需要通过 using 使用 Flush、Close 和 Dispose。你在某处有任何 for 循环吗?这段代码怎么称呼? XNA 中的 AFAIK 有每秒调用数百次来刷新图形的方法。 frameNumber++ 那是什么?如果这个计数器正在增长,那么这就是你的问题。将加载内容的代码移动到调用一次的方法中。
  • @oleksii:他想每帧输出一次,所以加载内容时不能只运行一次。
  • 'framenumber' 仅用于文件名。该代码每帧只调用一次。它正好在我场景的正常绘图程序的末尾

标签: c# out-of-memory xna-4.0


【解决方案1】:

根据thisTexture2D.SaveAsJpeg(以及Texture2D.SaveAsPng)有内存泄漏。

解决方案是(不幸的是)创建自己的纹理保存例程。

【讨论】:

  • yap - 该线程中的 bmpWriter 类确实起到了作用!谢谢:)
【解决方案2】:

听起来像这样,如果您对每一帧都执行此操作(每秒很多),您将写入 3 个图像 - 可能会建立一个队列以将其写入磁盘,并且程序运行的次数越多,备份越多问题。

【讨论】:

  • +1 对我来说似乎是合理的,因为我们无法自己分析它。
  • 也许尝试将保存的发生限制为每秒一次左右。看看有没有帮助。
  • 也许值得注意的解决方案是限制帧率,或者不保存每一帧。
  • 当我注释掉代码时,它以大约 300 fps 的速度运行。开启代码后,它只能以 4-5 fps 的速度运行,所以我怀疑它是否在某处排队。
  • @Mat 假设这不是问题,您能否扩展一下您的 OP 中的 xxRT 变量?它们保存 PNG,因此可能具有文件句柄和内部缓冲区。
【解决方案3】:

强制GC.Collect 只会收集有资格收集的内容...如果内存压力很高,GC 算法无论如何都会更频繁地为您执行此操作,所以这很少 解决方案。

它还表明您认为Stream s = File.Open 部分是问题所在。这包含在 using 语句中,之后立即超出范围,这意味着它被正确处理,并且很可能作为 Gen-0 对象保留 - 这意味着 GC 可以快速、高效地收集。

您还有两个问题区域:用于调用保存为 PNG 的 xxRT 变量,以及每帧保存三个 PNG 的行为本身。

顺便说一句,您可以改进代码的格式:

    using (Stream stream = File.OpenWrite("color_"+frameNumber+".png"))
    using (Stream stream2 = File.OpenWrite("light_" + frameNumber + ".png"))
    using (Stream stream3 = File.OpenWrite("spec_" + frameNumber + ".png"))
    {
        colorRT.SaveAsPng(stream, colorRT.Width, colorRT.Height);
        lightRT.SaveAsPng(stream2, lightRT.Width, lightRT.Height);
        specRT.SaveAsPng(stream3, specRT.Width, specRT.Height);
    }

【讨论】:

  • 您应该更改流名称... :)