【问题标题】:Why C# OutOfMemory exception in System.Drawing.dll?为什么 System.Drawing.dll 中出现 C# OutOfMemory 异常?
【发布时间】:2017-03-02 11:21:13
【问题描述】:

让我们简化模型。

class Container
{
    //other members
    public byte[] PNG;
}

class Producer
{
    public byte[] Produce(byte[] ImageOutside)
    {
        using (MemoryStream bmpStream = new MemoryStream(ImageOutside), 
            pngStream = new MemoryStream())
        {
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(bmpStream);
            bitmap.Save(pngStream, System.Drawing.Imaging.ImageFormat.Png);
            pngStream.Seek(0, System.IO.SeekOrigin.Begin);
            byte[] PNG = new byte[pngStream.Length];
            pngStream.Read(PNG, 0, (int)pngStream.Length);
            bitmap.Dispose();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            return PNG;
        }
    }
}

main函数不断使Container container = new Container();为container.PNG生成PNG,Queue.Enqueue(container) 使用 using() 子句根本不起作用。

虽然此重复大约 40 多次(它会有所不同),但它会引发异常。有时它是 OutOfMemoryException 有时它类似于“GDI+ 正常错误”(我不确定它的英文到底如何,我只是翻译了它)。 但是如果我尝试捕获异常并简单地忽略它,它仍然可以继续产生更多但不是无限的,只是更向前。 当第一个异常被抛出时,任务管理器中显示的占用内存只有大约 600 - 700 MB,最终停止在大约 1.2GB。我试过这个:

while (true)
{
    Bitmap b = new Bitmap(4500, 5000);
    list.Add(b);
    Invoke((MethodInvoker)delegate { textBox1.Text = list.Count.ToString(); });
}

虽然已经为程序分配了 99% 的内存(大约 11GB),但它从不抛出任何异常,并且所有发生的只是 textBox1 中的数字不再增加。

避免这种情况的方法可能是不要产生那么多东西,但我还是想知道内部原理和原因,谢谢你的帮助。

【问题讨论】:

  • 您需要处理创建的每个位图。您可能用完了图形句柄。
  • While(true) 是一个无限循环,它将尽可能快地运行,直到你中断。带有空文件夹的空 using 也不执行任何操作。
  • 不幸的是,System.Drawing 类有一个坏习惯,即有效地执行“是不是特定的糟糕情况 a?我会抛出这个异常。是不是特定的糟糕情况 b?我会抛出那个异常. 对于任何其他错误情况,我猜它是 OutOfMemory” - 即当底层问题与内存无关时,它经常抛出此异常。
  • @Hans Passant 我不明白,因为我只需要字节 [],我已经在流处理之前处理了位图。为什么流必须保持可读?什么是潜在蒸汽?没有下划线。
  • 您不断将大位图添加到您的列表中;这很可能不是关于内存,而是关于 'GDI 句柄'。观看他们在任务管理器中向上和向上直到你离开他们..

标签: c# .net winforms


【解决方案1】:

byte[] PNG = new byte[pngStream.Length]; 分配了很大一部分内存来存储图像。

following call没用,你已经dispose了流。

GC.Collect();
GC.WaitForPendingFinalizers();

PNG数组使用的内存无法释放,因为函数返回中有活动引用。

我建议返回一个流而不是字节数组。

否则在你调用Produce方法之后记得在再次调用之前删除对PNG的引用。

样本:

while (true)
{
    Byte[] b = new Byte[1000];
    b = this.Produce(b);
    //Use your array as you need, but you can't assign it to external property, otherwise memory cannot be released 
    b = null; //remove the reference, (in reality, in this example assign null is not necessary, because b will be overwritten at next loop.
    GC.Collect(); //Force garbage collector, probably not necessarry, but can be useful
    GC.WaitForPendingFinalizers();
}

平台编译会影响最大可用内存:

  • 在 32 位应用程序中,您最多有 2 GiB 的可用内存
  • 在 64 位应用程序中,您有 2 Tib 的可用内存,但 单个对象(类)不能超过 2 Gib。
  • 在 UWP 应用程序中,依赖于 设备
  • 任何 CPU 都会及时编译,当您启动应用程序时, 并且可以运行 32 位和 64 位,这取决于机器架构 和系统配置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多