C# PictureBox内存释放问题

13

我是C#的新手。我需要在工作线程中重复刷新GUI画框。图像是从一个轮询驱动程序的相机获取的,使用GetImage方法检索要显示的图像。即使我使用"using"指令分配位图并显式调用G.C,内存似乎永远不会被释放。

工作线程类似于以下内容:

   while (true)
    {
        // request image with IR signal values (array of UInt16)
        image = axLVCam.GetImage(0);
        lut = axLVCam.GetLUT(1);
        DrawPicture(image, lut);
        //GC.Collect();

    }

虽然 DrawPicture 方法大致如下:

   public void DrawPicture(object image, object lut)
{

  [...]

    // We have an image - cast it to proper type
    System.UInt16[,] im = image as System.UInt16[,];
    float[] lutTempConversion = lut as float[];

    int lngWidthIrImage = im.GetLength(0);
    int lngHeightIrImage = im.GetLength(1);

    using (Bitmap bmp = new Bitmap(lngWidthIrImage, lngHeightIrImage)) {

      [...many operation on bitmap pixel...]

        // Bitmap is ready - update image control

        //SetControlPropertyThreadSafe(tempTxtBox, "Text", string.Format("{0:0.#}", lutTempConversion[im[160, 100]]));

        //tempTxtBox.Text = string.Format("{0:00000}", im[160, 100]);
        //System.Drawing.Image.FromHbitmap(bmp.GetHbitmap());
        pic.Image = System.Drawing.Image.FromHbitmap(bmp.GetHbitmap());
    }
}

使用 pic.Image = System.Drawing.Image.FromHbitmap(bmp.GetHbitmap()); 这行代码会导致问题。实际上,如果将此行代码注释掉,则垃圾收集器可以正常工作。更好地说,问题似乎出在 System.Drawing.Image.FromHbitmap(bmp.GetHbitmap()) 上。有什么建议来解决这个内存泄漏问题吗?

非常感谢!

3个回答

21

Image实现了IDisposable接口,所以每当您创建一个Image实例并且不再需要它时,应该调用Dispose方法。您可以尝试在代码中将此行替换为:

pic.Image = System.Drawing.Image.FromHbitmap(bmp.GetHbitmap());

使用以下代码:

if (pic.Image != null)
{
    pic.Image.Dispose();
}
pic.Image = System.Drawing.Image.FromHbitmap(bmp.GetHbitmap());

在新图像被分配之前,这将处理掉先前的图像(如果有)。


问题在于他没有释放 GDI 对象。 - Yannick Motton
@Yannick:说得好。我想这两个问题都会给垃圾收集器带来问题。 - Fredrik Mörk
通过在 Image 上调用 Dispose,您可以处理在调用 FromHbitmap() 时分配的非托管资源。然而,当前的解决方案忽略了在调用 GetHbitmap() 时创建的非托管 GDI 对象。 - Yannick Motton
非常好的工作!我按照你的指示删除/处理了这两个对象。DeleteObject(gdiBitmap);和pic.Image.Dispose()现在一切都正常了!! 你救了我! 非常感谢! - picciopiccio
2
不要使用GetHbitmap(),没有意义。直接将bmp分配给Image属性。Bitmap派生自Image。 - Hans Passant
我已经正确处理了我的 Bitmap 变量,但是我忽略了一个 Graphics 对象。亲爱的读者们:不要忘记为您使用的每个对象正确地处理(或使用 using 语句)。它们很容易被忽视,但如果您这样做,您的诊断内存使用量将会不断增加。最后,在分配 PictureBox.Image 属性时,您可能需要分配对象的 .Clone()(需要进行强制转换)。 - Chad

8
事实上,您正在使用GetHbitmap创建一个GDI位图,根据msdn的说法:

您需要调用GDI DeleteObject方法来释放GDI位图对象使用的内存。

然后,FromHbitmap方法会复制GDI位图;因此,您可以在创建新图像后立即使用GDI DeleteObject方法释放传入的GDI位图。
因此,基本上我会添加:
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

...

IntPtr gdiBitmap = bmp.GetHbitmap();

// Release the copied GDI bitmap
if (pic.Image != null)
{
    pic.Image.Dispose();
}

pic.Image = System.Drawing.Image.FromHbitmap(gdiBitmap);

// Release the current GDI bitmap
DeleteObject(gdiBitmap);

我不确定您是否需要GDI位图来执行某种转换。如果不需要,您可以将位图分配给PictureBox的Image属性,并忽略前面的解决方案:

// Since we're not using unmanaged resources anymore, explicitly disposing 
// the Image only results in more immediate garbage collection, there wouldn't 
// actually be a memory leak if you forget to dispose.
if (pic.Image != null)
{
    pic.Image.Dispose();
}

pic.Image = bmp;

3

有几种方法可以从pbox中释放图像。我强烈建议不要使用pbox.Image = Image.FromFile...的方式。如果您没有使用FileStream并且想要从文件中读取它,请使用BitMap类。像这样:

Bitmap bmp = new Bitmap(fileName);
pbox.Image = bmp; // notice that we used bitmap class to initialize pbox.

...然后您想要释放图像文件 bmp.Dispose();
现在您可以删除、移动或对文件进行任何操作。


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