在将位图显示在图片框控件中后,请处理该位图。

3

我想知道为什么垃圾收集器无法追踪位图非托管部分使用的内存量,并在需要时释放此资源。

当后台线程中的循环调用以下函数一段时间后,我发现它引起异常:

    private delegate void setImageCallback();
    private void showFrame()
    {
        if (pboxCam.InvokeRequired)
        {
            this.Invoke(new setImageCallback(showFrame));
        }
        else
        {
            Bitmap bmp = new Bitmap(getBitmapFromCam());
            Graphics g = Graphics.FromImage(bmp);
            g.DrawRectangle(RectanglePen, 10, 10, 50, 30);
            g.Dispose();
            pboxCam.Image = bmp;
        }
    }

我尝试在函数末尾释放bmp,但是picturebox不再显示图像了。 下面的解决方案确实起作用,但有些我不喜欢的地方。你认为呢?

    Bitmap bmp;

    private delegate void setImageCallback();
    private void showFrame()
    {
        if (pboxCam.InvokeRequired)
        {
            this.Invoke(new setImageCallback(showFrame));
        }
        else
        {
            if (bmp!=null) bmp.Dispose();
            bmp = new Bitmap(getBitmapFromCam());
            Graphics g = Graphics.FromImage(bmp);
            g.DrawRectangle(RectanglePen, 10, 10, 50, 30);
            g.Dispose();
            pboxCam.Image = bmp;
        }
    }

仅提示“引发了异常”并不太有用。是哪种异常?请提供相应的消息、异常名称和堆栈跟踪。 - Sriram Sakthivel
你在循环中设置了 PictureBox 吗? - Hamlet Hakobyan
但是有些事情我不喜欢。你不喜欢什么,发生了什么? - C0d1ngJammer
@Hamlet Hakobyan,该函数本身是在循环中调用的,循环从相机中检索帧。 - Giuseppe Dini
@manuchao,它完美地工作了。只是我从来没有见过在函数开始之前丢弃一个对象并在分配新实例之前这样做。我只是想知道是否有原因。 - Giuseppe Dini
显示剩余2条评论
1个回答

2

你做得很好。GC正在跟踪 Bitmap 对象,并且最终它会释放它。关键词是“最终”。

要回收托管的 bmp 实例,首先必须遇到内存压力。对于gen-0收集或类似的情况,大约在1 MiB左右发生。我不确定确切的数字,但关键因素是这是很多的。在甚至达到gen-0收集之前,您将拥有数千个位图,甚至更多 - 并且通常需要一个gen-2来执行finalizer(尽管请谨慎 ,我不知道我在哪里找到了那些信息)。

即使GC最终运行(实际上,在此之前,您很快就会耗尽内存),它也会在单独的线程上开始执行finalizer。它不知道未受管制的资源,因此必须依赖于finalizer代码,它按顺序运行。它根本不知道分配了多少未受管控的内存,并且无法在其内存检查中计算。

因此,关键是始终在IDisposable对象上运行Dispose。在耗尽内存之前,GC甚至可能没有机会开始finalizer,因为它仅由托管内存压力引起。它不知道未受管控的内存(如果有)。这不是GC的错,它确实无法判断 - 毕竟,在您的情况下,内存是共享的。 GC无法知道它甚至是否应该释放该内存 - 这取决于Bitmap.Finalize

这也是为什么您真正希望使使用任何IDisposable或未受管控内存的所有内容也成为IDisposable的原因 - 以确保调用者可以尽快释放本机资源。

这不仅适用于未受管控的内存 - 等待GC处置例如文件句柄或套接字连接是一个坏主意,因为您确实不想比必要的时间更长地持有这些东西。


我同意你的观点,但是OP说它抛出了 OutOfMemoryException,对此我持怀疑态度。 :( - Sriram Sakthivel
@SriramSakthivel 我已经扩展了我的答案,希望能够回答你的怀疑 :) - Luaan
@Sriram Sakthivel,我有一个显示屏异常的打印,但最好不要在这里展示。相信我:) 它发生的行是Bitmap bmp = new Bitmap(getBitmapFromCam()); - Giuseppe Dini
我的问题是为什么没有实现一种机制,可以将GC的压力增加到分配给像素数据的内存量相等的程度(这可以很容易地计算出来)? - Giuseppe Dini
@GiuseppeDini .NET并不知道这一点。GDI+处理这个问题,这是一个本地的、非托管的库。即使它知道,它也不是托管内存压力,所以它与GC没有任何关系。它甚至不一定需要分配任何东西,或者完全不同的应用程序可能使用相同的位图数据。非托管就是这样——无法管理。你无法真正跟踪内存。我很高兴GC不会猜测:)本地句柄被包装在托管句柄中的事实可以避免泄漏(很好),但不会导致内存耗尽(你的错)。 - Luaan

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