处理字体文件真的有必要吗?

18
我知道最佳实践是调用任何实现IDisposable的对象的Dispose方法,特别是包装有限资源(如文件句柄、套接字、GDI句柄等)的对象。

但我遇到了一个问题,我有一个带有Font的对象,如果我要让Font得到释放,我必须通过多个对象图层传递IDisposable,并检查很多用法。我在想这是否值得复杂度。

如果Font包装了HFONT,那就没什么关系了,因为GDI资源是系统全局的。但Font没有包装GDI句柄;它是GDI+,是一个完全独立的系统,并且据我所知,是进程局部的,不像GDI那样是系统全局的。而且,与Image不同,Font不会保留文件系统资源(至少我不知道)。

所以我的问题是:让Font被垃圾回收的真正成本是什么?

我知道我将承担终结器的一小部分开销,但如果“泄漏”的Font数量很少(比如半打),那么这个开销实际上是几乎不可察觉的。除了终结器之外,这似乎并没有什么不同于分配中等大小的数组并让GC清理它 - 它只是内存。

让Font被GC回收有我不知道的成本吗?

6个回答

5
问题在于垃圾回收只会在出现内存压力时才会发生。通常情况下,非托管句柄比内存更受限制,因此在GC发生之前可能会用尽句柄,从而导致错误。
但对于一个或两个字体实例来说,这不会对您造成太大的影响。
更大的问题是一些对象是共享的,并且不应该(或无法)过早地被处理...

"通常,未受管理的句柄比内存更受限制" -- 当然。但对于 GDI+ 字体句柄来说也是如此吗?这是我的问题的一部分。" - Joe White

5
简单回答:如果只是几个对象,那就不需要。如果是很多对象,那就需要。如果你的应用程序已经对垃圾回收器造成了压力,那么就需要。我建议使用perfmon查看周围对象的数量和升级到更高一代的数量,然后决定是否需要进行手动内存管理。

5
为什么你用完之后不把它处理掉呢?我们有了清洁工并不意味着我们应该到处乱扔垃圾。但是,在所给出的例子中,如果字体需要在对象的生命周期内使用,则在该对象的dispose中处理该字体。我的代码还有很多简化的地方,但这并不能证明那些更改是正确的 - 有时候,有些事情你应该去做,即使它很痛苦。
及时处理自己的东西总是一个好习惯。当你不再需要某些东西时,请将其处理掉。这样可以避免不良的竞争条件、内存不足异常、绘图故障和冗长的、处理器密集型的垃圾收集。
我发现最好的做法是,在你不再需要可处理的对象时进行处理,除非有正当的理由不这样做(比如你不拥有该对象)。跟踪问题的根本原因比一开始就进行防御性编码要困难得多。
关于Font,MSDN说

在释放 Font 的最后一个引用之前,始终调用 Dispose。否则,它正在使用的资源将不会被释放,直到垃圾回收器调用 Font 对象的 Finalize 方法。

虽然没有明确说明这些资源是什么,但它们明确指出应该这样做,这增加了调用 Dispose 的重要性。


7
为什么不处理它呢?嗯,OP已经说了原因:因为这将极大地简化他的代码。 - Iraimbilanja
2
当控件的Font属性(或Picture属性等)设置为我持有的对象时,在哪些情况下控件会复制该对象(在这种情况下,我应该处理我的对象并让它自己处理),在哪些情况下控件希望继续使用传入的对象?如果我可以选择,我希望有一种指定控件是否应该拥有传入对象的方式,但由于没有这样的方式,那么应该怎么做呢? - supercat
@supercat:文档应该告诉你关于所有权的规则。然而,你至少应该假设在控件正在引用字体时,不应该将其Dispose。我希望控件在被Dispose时会Dispose字体,但我不确定。 - Jeff Yates
2
Control.Font属性文档没有提到所有权的问题;实验表明,该属性与处理无关。控件不会处理其字体属性,但如果分配的字体被处理(即使在分配之前处理!),它也不会关心。似乎如果想要一个字体仅用于设置控件字体属性,则可以预处理字体,但这种做法感觉不对。我不确定如何最好地处理Picturebox的Image属性,因为它们确实关心处理。 - supercat

3
有关于处理任何东西的重要性,这真的很重要吗?在我看来,当你开始问这种问题时,似乎你的代码存在设计问题。你应该始终处理不再需要的东西——这被称为负责任的编程。
解决问题的可能方案:
- 不要传递像“字体”这样的对象。在一个地方(一个类)实现使用字体的逻辑,将字体作为该类的字段,并为该类实现IDisposable。 - 实现一个字体缓存类——而不是在代码中到处使用new操作符创建新的Font对象,使用此类获取所需的字体。该类可以具有重用现有字体的逻辑(如果可能),或者将最后10个字体保存在内存中,并处理其他字体。为缓存实现IDisposable,在您的应用程序生命周期中调用一次。

字体缓存类如何知道字体不再使用?需要将字体“返回”到缓存中吗? - Arafangion
是的,必须有一种告诉它你不再需要字体的方法。我通常使用某种实现IDisposableLease类来完成这个操作,在Dispose()方法中,它会联系缓存并告诉它减少引用计数。同样的模式也可以用于工厂。 - Igor Brejc
那不是回到原点了吗?尽管如此,你现在有一个可以管理你的昂贵字体的经理,但这确实引出了一个问题:为什么你不应该假设构造函数/工厂和垃圾收集器在大多数情况下都能完成任务呢? - Arafangion
区别在于您实际上不需要了解(或关心)资源的情况。 工厂可以销毁它们或仅将它们缓存以供以后使用。 而且,您稍后可以更改行为。您是否真的需要此模式取决于您的资源的成本和您需要它们的频率。 在复杂的GDI渲染中,字体经常被重用(单个Paint调用可达数千次),这绝对可以加快速度。 - Igor Brejc

1

Finalizers 是专门集成到类中的,因为清理是必要的。无论您需要清理大量还是少量的对象,最好的做法是对它们进行清理。

GC 被构建成具有自己伪智能的形式。通过适当地处理您的对象,您可以允许 GC 做它应该做的事情。

但是,如果您正在创建大量字体对象并且需要处理所有这些对象,则可能有利于定期调用适当的代(可能是第 0 代)上的 GC,以便根据您实例化的其他对象类型启动自行处理 GC 清除。您的目标应该是保持您知道您不会长时间使用的对象不会被提升到更高的代数,这样可以使 GC 的工作变得简洁而有效。

只要凭借您最好的判断力,就可以做得很好。但是确实需要作为正常操作处理任何具有 Finalizers 的对象。


-2

我至少还有一个正在运行的应用程序使用了.NET运行时。我不停地收到OutOfMemoryExceptions异常。让你的应用程序表现良好,这样其他应用程序在无法获得足够资源时就不会抛出异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接