【问题标题】:Is it important to dispose SolidBrush and Pen?处理 SolidBrush 和 Pen 重要吗?
【发布时间】:2010-12-21 14:29:57
【问题描述】:

我最近遇到了this VerticalLabel control on CodeProject

我注意到 OnPaint 方法创建但不处理 Pen 和 SolidBrush 对象。

这重要吗?如果重要,我如何证明它可能导致的任何问题?

编辑

这不是关于 IDisposable 模式的一般问题。我了解调用者通常应该在任何实现 IDisposable 的类上调用 Dispose。

我想知道的是,当 GDI+ 对象没有像上面的例子那样被处理时,会出现什么问题(如果有的话)。很明显,在链接的示例中,可能会在垃圾收集器启动之前多次调用 OnPaint,因此可能会耗尽句柄。

但是我怀疑 GDI+ 在某些情况下会在内部重用句柄(例如,如果您使用 Pens 类中特定颜色的笔,它会被缓存并重用)。

我想了解的是,链接示例中的类似代码是否能够避免忽略调用 Dispose。

如果不是,请查看一个示例,说明它可能导致什么问题。

我应该补充一点,我经常 (including the OnPaint documentation on MSDN) 看到无法处理 GDI+ 对象的 WinForms 代码示例。

【问题讨论】:

  • 是的,不幸的是,许多 MSDN 示例仍然无法处理钢笔和画笔。

标签: .net winforms idisposable


【解决方案1】:

对象应该在它们超出范围后由垃圾回收处理,只要它们没有被其他任何东西引用。

证明这种情况是否发生的最简单方法(无需进行任何代码更改)是在表单上使用大量此类控件,并查看应用程序使用的内存是否不断增长或保持稳定。

【讨论】:

  • 我知道他们最终会被 GC,但是什么时候?我不希望内存增长,而是使用的句柄数量。为了测试所有情况,我需要确保创建不同颜色的画笔/钢笔。
【解决方案2】:

它只是泄漏非托管资源(笔/画笔),直到相应的托管对象被垃圾收集器回收。所以你浪费了一些以后不再使用的手柄。

这没什么大不了的,因为Finalize() 会调用Dispose(),但通常应该尽早释放非托管资源。

【讨论】:

  • 是的,我知道 GC 最终会启动,但是,由于 OnPaint 可以被频繁调用,是否存在“浪费”多个句柄的风险。
  • 可能,是的。这取决于 .NET 将使用多少堆,因此它取决于 GC 启动的频率。但是,OnPaint 方法可能只会生成将更频繁地回收的第 0 代对象。
  • 称其为“泄漏”听起来是错误的。我认为这根本没有错。
【解决方案3】:

当然,这很重要。当不再需要实现 IDisposable 的类的所有实例时,释放它们很重要。类在使用非托管资源(即不由 .NET Framework 处理的操作系统资源)时被定义为 IDisposable。

如果你不手动释放对象,那么这些非托管资源将不会被释放,直到垃圾收集器调用对象终结器(这只会在类正确实现 the Dispose pattern 时发生),这可能非常很晚,因为垃圾收集器仅在检测到实际内存不足时才运行。因此,当不处理对象时,您会保留操作系统资源,否则这些资源可能会被其他应用程序使用。

可以在此处找到有关此主题的讨论:http://agilology.blogspot.com/2009/01/why-dispose-is-necessary-and-other.html

【讨论】:

  • 我的问题不是关于 IDisposable 模式,我理解它的用途以及为什么调用者通常应该调用 Dispose。请参阅编辑问题。
【解决方案4】:

此外,我会说最好使用现成的集合: 系统.绘图.笔。 和 系统.绘图.画笔。 如果您不以特殊方式自定义它们。

【讨论】:

  • 是的,我同意。但在链接的示例中,这是不可能的,因为钢笔和画笔只包含固定数量的预定义颜色的元素。
【解决方案5】:

FWIW,在 GDI 中有“库存”对象。当您创建一个股票对象时,您没有删除它,因为它是由操作系统“拥有”的。

您可能已经了解库存对象,但这里有一个link,其中包含一些细节。

我不知道 GDI+ 中是否有类似的“库存”对象。我只是简单地搜索了一下,并没有找到任何这样的参考。

作为测试,我编写了一个带有计时器回调(设置为每 10 毫秒触发一次)的小型 WinForms 程序,如下所示:

private void timer1_Tick(object sender, EventArgs e)
{
  byte r = (byte)rnd.Next(0, 256);
  byte g = (byte)rnd.Next(0, 256);
  byte b = (byte)rnd.Next(0, 256);

  System.Drawing.SolidBrush sb = new SolidBrush(Color.FromArgb(0,r,g,b));
}

如果我让它运行,它会慢慢消耗内存。通过观察 TaskManager(不是衡量它的最准确方法),每次任务管理器更新(以最高更新速率),内存使用量往往会增加(在我的机器上,使用 .NET 4.0 VS2010 构建)大约 20k 字节。如果我在刷机上调用 Dispose,每次任务管理器更新内存使用量往往会增加约 8k。

几乎不是一个确定的测试,但如果不处置 SolidBrush,它似乎表明随着时间的推移内存使用量会增加。有趣的是,在测试运行时,我的句柄和 GDI 对象都没有增加(在任何一种情况下)。根据过去泄漏 GDI 资源的经验,我预计 GDI 对象可能会增长,尤其是在非 Dispose 情况下。

无论如何,这也许是信息丰富的,也许不是。

【讨论】:

  • 查看任务管理器中的句柄数量将是可行的方法,你应该看到它上升和下降(在 GC 之后)我想
【解决方案6】:

我需要修复应用程序中的一些内存泄漏,因此我正在进行一些调查。简而言之,似乎在大多数情况下,您只需稍高的内存使用就可以侥幸成功。

下面是一个病态的案例。我在任务管理器中监控我的测试应用程序(我知道粗略)并观察了内存(私有工作集)、句柄、用户对象和 GDI 对象列。 Button1 的点击比 Button2 的点击导致更高的内存使用。

如果我快速且持续地单击 Button1,当内存达到大约 1.6GB 时,我可能会导致“System.OutOfMemoryException”。无论我多么疯狂或持续地点击,Button2 的大小都不会超过 12MB。

我在一个 win7 64 位机器上构建一个 vs 2010 .net 4 客户端配置文件 winforms 应用程序。显然你通常不会构建数以百万计的画笔......

问候大卫

private void button1_Click(object sender, EventArgs e) {
    for (int i = 0; i < 500000; i++) {
        SolidBrush b = new SolidBrush(Color.FromArgb(2, 32, 43, 128));
    }       
}

private void button2_Click(object sender, EventArgs e) {
    for (int i = 0; i < 500000; i++) {
        using (SolidBrush b = new SolidBrush(Color.FromArgb(2, 32, 43, 128))) {
        }
    }
}

【讨论】:

  • 似乎暗示应该始终使用using!除非编译器完全删除第二个函数,因为它可能知道它什么都不做?
猜你喜欢
  • 2020-03-29
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-29
  • 2011-10-16
  • 1970-01-01
相关资源
最近更新 更多