我想知道为什么垃圾收集器无法跟踪位图的非托管部分使用的内存量,并在需要时释放此资源。
我发现它是在后台线程中的一个循环调用以下函数导致异常一段时间之后:
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,但是图片框不再显示图像了。 下面的解决方案确实有效,但有一些我不喜欢的东西。你觉得怎么样?
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;
}
}
答案 0 :(得分:1)
你做得对。 GC正在跟踪Bitmap
对象,它最终会释放它。关键词是"最终"。
要回收托管的bmp
实例,首先必须遇到内存压力。对于gen-0集合,大约1 MiB发生这种情况。我不确定准确的数字,但关键因素是它很多。在你进入gen-0集合之前,你将拥有成千上万的位图,如果不是更多的话 - 你通常需要一个gen-2来执行终结器(虽然拿了一粒盐,我没有想法我在哪里找到了这些信息。)
即使GC最终运行(实际上,在此之前很久就会耗尽内存),它将开始在单独的线程上执行终结器。它不知道非托管资源,所以它必须依赖于串行运行的终结器代码。它根本不知道分配了多少非托管内存,并且无法在内存检查中找到它。
因此,关键是始终在Dispose
个对象上运行IDisposable
。在内存不足之前,GC可能甚至没有机会启动终结器,因为它只是由托管的内存压力引起的。它不知道非托管内存(如果有的话)。这不是GCs的错,它实在无法说明 - 毕竟,在你的确切情况下,内存是共享的。 GC无法知道它是否应释放该内存 - 由Bitmap.Finalize
决定。
这也是你真正想要制作使用任何IDisposable
或非托管内存(也是IDisposable
)的所有内容的原因,以确保调用者可以尽快释放本机资源可能的。
这并不适用于非托管内存 - 等待GC处理例如。文件句柄或套接字连接是一个坏主意,因为你真的不想持有那些超过必要的时间。