由于我上个月有一个非常明显的非托管资源问题,我对内存泄漏问题有点过分了解。 我只是在一个非常简单的测试应用程序上编码,上面有一个带有两张不同图片的按钮,并注意到我不太确定我是否有一个"问题"这里与否...
如果我有2个图片资源Pic1和Pic2以及一个ImageButton-Object,它只是从UserControl继承的一个对象并且具有更改的OnPaint:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//stuff
if (this.keyStatus))
{ this.imageButton.DefaultImage = Resource1.Pic1; }
else
{ this.imageButton.DefaultImage = Resource1.Pic2; }
e.Graphics.DrawImage(this.defaultImage, this.ClientRectangle);
}
除了OnPaint不是一个分配DefaultImage的好地方(它只是在这里向我展示我在短代码中的意思),我只是在这里引用我的预编译资源,是吗?如果我用new Bitmap(Resource1.Pic1)
调用它,我就不会创建副本。
因此,如果我每隔5秒更改一次keyStatus,我会在屏幕上看到一张非常烦人的图片,并且会有很多变化,但没有问题,图片会不时变换或不可见,会泄漏内存。正确的吗?
非常感谢!
答案 0 :(得分:0)
对象引用的工作原理
假设你有一个随机对象。对象是类类型(不是值类型)而不是IDisposable
。基本上这意味着以下内容:
// y is some object
var x = y;
现在,x不会复制y中的所有数据,而只是对y的内容进行新的引用。简单。
为了确保不会出现内存泄漏,GC会跟踪所有对象并(定期)检查哪些对象可以访问。如果某个对象仍然可以访问,则不会被删除 - 如果没有,则会被删除。
然后是非托管代码
只要您坚持托管代码,一切都很好。当你遇到非托管代码(比如:GDI +,这是许多System.Drawing东西的本机对应物)时,你需要做额外的簿记来摆脱代码。毕竟,.NET运行时对非托管数据一无所知 - 它只知道有一个指针。因此,GC将清除指针,但不清除数据 - 这将导致内存泄漏。
因此,来自.NET的人添加了IDisposable
。通过实现IDisposable,您可以实现额外的(非托管)清理,例如释放非托管内存,关闭文件,关闭套接字等。
现在,GC了解终结器,它们是作为Disposable模式的一部分实现的(详情:https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx)。但是,您通常不希望等待GC运行来清理非托管资源。因此,在清理对象并拥有非托管资源时调用Dispose()
通常是个好主意。
与System.Drawing.Bitmap
一样,实现IDisposable
。
在大多数情况下,您只需将IDisposable
包裹在using
语句中,该语句将调用' Dispose()'在一个很好的try / finally子句中为你服务。 e.g:
using (var myBitmap = new Bitmap(...))
{
// use myBitmap
}
// myBitmap including all resources are gone.
资源位图怎么样
@HansPassant指出,每次访问位图属性时,资源位图都会生成新的Bitmap
。这基本上意味着位图被复制并需要处理。
换句话说:
// Free the old bitmap if it exists:
if (this.imageButton.DefaultImage != null)
{
this.imageButton.DefaultImage.Dispose();
this.imageButton.DefaultImage = null;
}
// assign new imageButton.DefaultImage
因此,这解决了内存泄漏问题,但会为您提供大量复制的数据。
如果您不想处理
这就是为什么我对汉斯的评论感到惊讶的部分:)基本上你每次都给一个按钮分配一个Bitmap
,所以你不想一遍又一遍地复制数据 - 这没什么意义。
因此,您可能会想到将资源包装成一个静态的'容器,根本不要解除分配:
static Bitmap myPic1 = Resource1.Pic1;
static Bitmap myPic2 = Resource1.Pic2;
...
if (this.keyStatus))
{
this.imageButton.DefaultImage = myPic1;
}
else
{
this.imageButton.DefaultImage = myPic2;
}
这样可行,但如果您在某些时候决定生成图像,也会给您带来问题。为了说明,我们改变代码如下::
if (this.keyStatus))
{
this.imageButton.DefaultImage = myPic1; // #1 don't dispose
}
else
{
Bitmap myPic3 = CreateFancyBitmap(); // #2 do dispose
this.imageButton.DefaultImage = myPic3;
}
现在,问题在于组合。 myPic1
是一个静态对象,不应该被处置。另一方面,myPic3
不是,应该被处置。如果你打电话给Dispose()
,你会在#1处得到一个令人讨厌的例外,因为数据不再存在。没有正确的方法来区分这两者。