图像资源记忆

时间:2016-03-23 11:56:30

标签: c# .net user-interface memory-leaks

由于我上个月有一个非常明显的非托管资源问题,我对内存泄漏问题有点过分了解。 我只是在一个非常简单的测试应用程序上编码,上面有一个带有两张不同图片的按钮,并注意到我不太确定我是否有一个"问题"这里与否...

如果我有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,我会在屏幕上看到一张非常烦人的图片,并且会有很多变化,但没有问题,图片会不时变换或不可见,会泄漏内存。正确的吗?

非常感谢!

1 个答案:

答案 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处得到一个令人讨厌的例外,因为数据不再存在。没有正确的方法来区分这两者。