避免配置系统定义的Pen和Brush实例

时间:2010-07-08 04:56:26

标签: winforms gdi+ idisposable system.drawing

我理解最佳做法是在PenBrush的实例上调用Dispose(),除非它们已设置为系统预定义值(例如System.Drawing.BrushesSystem.Drawing.PensSystem.Drawing.SystemBrushes

尝试部署系统定义的资源会导致抛出异常。

除了在try / catch中包装Dispose()调用之外,这些资源中的一个是否引用了系统定义的值或用户定义的值,这似乎并不明显。

8 个答案:

答案 0 :(得分:30)

这是我所知道的一个老问题,但我一直在研究涉及GDI对象的资源泄漏问题,而且几乎接受答案中的每个陈述都是错误的。为了通过搜索找到这个问题的后来读者的准确性,正如我所做的那样:

  

无需调用Dispose。

这句话具有误导性。虽然从技术上讲你不会打电话给Dispose,但是一旦你完成它,就不会丢弃你拥有的刷子或笔是非常糟糕的做法。< / p>

原因是:存在一个硬限制(默认情况下设置为一万,尽管可以通过注册表黑客增加)可以在流程中出现的GDI对象数量 - 而不是应用程序域,请注意 - - 垃圾收集器可能无法比泄漏更快地完成资源的最终确定。托管内存分配产生收集压力,但没有相应的终结压力

  

垃圾收集的目的是消除这些要求。

不是。垃圾收集的目的是模拟具有任意大量内存的环境。

  

IDisposable的主要目的之一是允许类在资源有限的环境中清理非托管资源。

这是正确的。

  

如果不调用Dispose()方法,则在对象完成初始化并在垃圾回收期间处理时,将清除类的非托管资源。

这有时是正确的;它对GDI对象是正确的。对于持有非托管资源的类来说,实现最终化语义作为后备是一种很好的做法;为那些遵循接受的答案中给出的错误建议的人提供额外的保护。 你不应该依靠终结者来避免你的错误;你应该处理你的资源。

你应该只依靠终结者来应对疯狂的异常情况。考虑例如:

Brush myBrush = MakeMeABrush();
DoSomethingWithBrush(myBrush);
myBrush.Dispose();

假设在分配刷子之后抛出线程中止异常,但分配到myBrush之前。没有try-catch-finally的组合可以让您通过Dispose清理该画笔;你必须依靠终结者。这就是终结器的用途:完全疯狂的情况,你无法自行处理。 这不是一个邋。的借口。

  

如果你&#34;必须&#34; call dispose,你不知道画笔实例是否是一个&#34;系统 - &#34;或者#&#34;正常 - &#34;刷然后你将不得不使用try ... catch块。

虽然这在技术上是正确的,但它完全忽略了这一点。 如果您处于不知道自己是否拥有画笔的情况,那么您的程序设计存在错误。不要用try-catch阻止你的错误!修复错误!

这种情况对于所有明确管理的资源都是通用的:提供资源的实体和消耗资源的实体负责清楚地记录两个拥有资源的哪个清理资源。如果您不知道自己是否拥有自己已经获得过的刷子,那么某人并没有完成他们负责的任务,即防止出现这种情况

如果您决定的合同是提供资源的实体负责在以后清理它,那么您的消费者不应该处理所有的刷子,因为那是违约;如果需要,生产者将清理它。

如果您决定的合同是生产者和消费者都要释放资源,那么消费者必须在上传递的每个刷子上调用Clone以确保他们拥有一个安全的可支配资源,生产者也继续拥有一个有效的资源。

如果您决定的合同很可能是消耗资源的实体负责清理它,那么提供者必须总是递给您一个可以安全处理的刷子,并且需要处理资源本身。由于提供程序知道他们是自己制作了刷子还是从系统中获取了刷子,因此提供者必须在系统刷子上调用Clone()以获得可以安全处理的刷子,然后传递< em>那给消费者。

但合同&#34;没有人清理它,我们希望最好的&#34;是一份非常糟糕的合同。

  

不需要在Dispose()SystemBrushes上致电SystemPens,因为GDI +图书馆会处理这些资源。

这个解释是一个实际上没有解释的解释。处理这些画笔之一是非法的原因是因为画笔的生命周期等于appdomain的生命周期

  

该类的备注部分将记录是否需要调用Dispose方法。

这句话是错误的。您应该假设Dispose IDisposable资源始终是必要的,除非您有充分的理由相信其他情况。文件中没有一行并不表明处理是不必要的。

以积极的方式结束:

  

如果我有一个图形密集型应用程序或类,那么我将缓存我需要的笔和画笔的实例。我在应用程序或类的整个生命周期中使用它们。

这是一个很好的做法。如果创建的画笔和笔的数量相对较少并且一次又一次地使用相同的画笔和笔,那么永久地缓存它们是很有意义的。由于它们的生命周期到程序结束,因此无需处置它们。在这种情况下,我们没有处理垃圾,因为它不是垃圾;它很有用。当然,高速缓存的GDI对象应该被处理掉。同样,追求缓存策略并不是参与不良行为的借口。

答案 1 :(得分:4)

完成创建的Graphics对象后,必须通过调用Dispose方法来处置它。 (对于许多不同的GDI +对象,此规则都适用。)请勿将其保留在下雨天,因为以后它不会有效。你必须,必须,当你完成它时必须处理它。如果不这样做,可能会导致图像损坏,内存使用问题或更糟糕的国际武装冲突。因此,请妥善处理所有图形对象。

如果在事件中创建Graphics对象,则在退出该事件处理程序之前,确实需要处理它。无法保证Graphics对象在以后的事件中仍然有效。此外,可以随时重新创建另一个Graphics对象。

从这里来 http://msdn.microsoft.com/en-us/library/orm-9780596518431-01-18.aspx

答案 2 :(得分:2)

首先,你总是应该尽可能地处理画笔,而不是把它留给垃圾收集器。虽然GDI最终会处理这些事情(假设图书馆被正确关闭),但是没有人知道这可能是什么时候。事实上,我的理解是刷子手柄长期固定。在长时间运行的应用程序中,您正在查看事实上的内存泄漏。当然,在一个不会长时间运行的小型应用程序中,或者只是很少创建画笔的应用程序中,您可以让系统处理它,但这让我觉得很草率。

作为一般规则,每当我创建一个画笔时,我都会在using语句中这样做。这会自动调用刷子上的处理,而不必担心它。作为一个额外的好处,因为你在声明中创建了画笔,你知道它不是预定义的画笔。无论何时创建和使用非预定义的画笔,都要将块包装在一个使用中,您根本不必担心它。实际上,您甚至不需要显式调用Dispose,因为即使在异常的情况下块也会这样做。

答案 3 :(得分:2)

简短的回答是,如果您创建它,要么委派责任来清理或清理对象。您可以通过让垃圾收集器挂起来创建GDI资源“泄漏”。他们最终可能会被清理干净,但他们并没有做任何好事。即如果你没有打开“关闭”或处理打开的文件,文件将保持锁定状态,直到GC“绕过它”

答案 4 :(得分:0)

我可能错了,但我认为您可以假设预定义画笔和笔的生命周期(和处理)不是您的应用程序的责任,并且将由系统处理。

简而言之:不要在预定义的东西上调用Dispose。 :)

答案 5 :(得分:0)

唯一要记住的是练习不要使用系统笔/画笔作为方法的参数。

答案 6 :(得分:0)

为了完整起见,使用Reflector我看到System.Drawing.Pen和System.Drawing.SolidBrush类有一个名为'immutable'的私有字段。

当对象引用系统定义的资源时,此字段似乎设置为true,因此您可以使用反射来仔细检查此字段的值,以决定是否在其上调用Dispose()。

答案 7 :(得分:-9)

无需致电Dispose。垃圾收集的目的是消除这些要求。

IDisposable的主要目的之一是允许类在资源有限的环境中清理非托管资源。如果不调用dispose方法,则在对象最终化并在垃圾回收期间处理时,将清除类的非托管资源。

如果您“必须”调用dispose并且您不知道画笔实例是“系统”还是“普通”画笔,那么您将不得不使用try ... catch块。

  • 不需要在SystemBrushesSystemPens上调用dispose,因为GDI + Library会处理这些资源。
  • 可以处置SystemFontsSystemIcons

该类的备注部分将记录是否需要调用Dispose方法。 如果备注部分建议调用Dispose方法,那么我会这样做。

一般情况下,我不会在钢笔和刷子上使用处理。如果我有一个图形密集型应用程序或类,那么我将缓存我需要的笔和画笔的实例。我在应用程序或类的整个生命周期中使用它们。如果我没有这样做,那么图形绘画性能将试图创建和处理所有这些对象如此频繁和如此频繁。 (嗯......现在我考虑一下,性能可能就是为什么我们不能处理SystemBrushes和SystemPens,但可以处理SystemFonts和SystemIcons。甚至框架也会缓存SystemBrushes和SystemPens。)