【问题标题】:How important is disposing a Font, really?配置字体有多重要,真的吗?
【发布时间】:2010-10-19 15:16:02
【问题描述】:

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

但是我遇到了这样一种情况,我有一个具有字体的对象,我必须通过几层对象来探测 IDisposable,并查看很多用法,以确保我总是处理字体.我想知道它是否值得这么复杂。

如果 Font 包装了 HFONT 是一回事,因为 GDI 资源是系统全局的。但是 Font 不包装 GDI 句柄;它是 GDI+,它是一个完全独立的系统,据我所知,它是进程本地的,而不是像 GDI 那样的系统全局的。与 Image 不同,Font 永远不会占用文件系统资源(我知道,无论如何)。

所以我的问题是:让字体收集垃圾的真正成本是多少?

我知道我会对终结器产生一点影响,但如果“泄露”字体的数量很少(比如六个),那么这种影响真的不会很明显。除了终结器之外,这似乎与分配一个中等大小的数组并让 GC 清理它没有太大区别——它只是内存。

让字体获得 GCed 是否有我不知道的成本?

【问题讨论】:

    标签: .net fonts garbage-collection gdi+ finalizer


    【解决方案1】:

    简单的答案:如果只有几个,那么不会。如果它很多,那么是的。如果您的应用程序已经对垃圾收集器施加压力,那么可以。我会使用 perfmon 查看周围的对象数量,以及提升到更高代的数量,然后再决定。

    【讨论】:

      【解决方案2】:

      问题是垃圾收集仅在内存压力存在时发生。通常,非托管句柄比内存更受限制,因此您可能会在 GC 发生之前用完句柄,从而导致错误。

      但对于一两个 Font 实例,它不会对您造成太大伤害。

      一个更大的问题是一些对象是共享的,不应该(或不能)过早地处置......

      【讨论】:

      • “通常,非托管句柄比内存更受限制”——当然。但是对于 GDI+ 字体句柄来说真的是这样吗?这是我的问题的一部分。
      【解决方案3】:

      你为什么不做完后把它扔掉?仅仅因为我们有清道夫,并不意味着我们应该四处乱扔垃圾。但是,在给出的示例中,如果对象的生命周期需要字体,则在该对象的 dispose 中处置该字体。有很多事情也可以简化我的代码,但这并不能证明这些更改是合理的 - 有时有些事情你应该做,即使这很痛苦。

      自己收拾东西总是一个好主意。当您不再需要某物时,请丢弃它。这样您就可以避免令人讨厌的竞争条件、内存不足异常、绘图故障以及冗长的处理器密集型垃圾收集。

      我发现,如果您不再需要一次性物品,最好将其丢弃,除非有正当理由不这样做(例如您不拥有该物品)。追查问题的根本原因比预先编写防御性代码更难。

      关于字体,MSDN says:

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

      它没有说明资源是什么,但它们明确声明应该隐含地完成这一事实增加了调用 Dispose 的重要性。

      【讨论】:

      • 你为什么不把它扔掉?好吧,OP 确实说明了原因:因为它会极大地简化他的代码。
      • 当一个控件的Font属性(或者Picture属性等)被设置为我持有的一个对象时,在什么情况下该控件会复制该对象(这种情况我应该处理, 并让它处理它), 在什么情况下控件希望继续使用传入的对象?如果我有我的 druthers,就会有一种方法可以指定控件是否应该承担传入对象的所有权,但既然没有,应该怎么做?
      • @supercat:文档应该向您说明有关所有权的规则。但是,您至少应该假设当控件引用字体时,您不应该处置它。但是,我希望控件在处理字体时会处理字体,但我不确定。
      • Control.Font 属性文档没有说明所有权;实验表明,特定财产与处置无关。控件不会释放其字体属性,但不会关心分配的字体是否已释放(即使它在分配之前已释放!)。看起来如果一个人想要一种字体只是为了设置控制字体属性,可以预先配置字体,但不知何故感觉不对。不过,我不确定如何最好地处理 Picturebox 的 Image 属性,因为它们确实关心处置。
      【解决方案4】:

      处理任何东西真的很重要吗?恕我直言,当您开始提出此类问题时,听起来您的代码中存在设计问题。你应该总是丢弃你不再需要的东西——这叫做负责任的编程。

      您的问题的可能解决方案:

      • 不要绕过像Fonts 这样的对象。 在一处实现字体使用逻辑(一个类),将 Font 添加为该类的字段并为该类实现IDisposable

      • 实现字体缓存类 - 不要在代码中使用 new 运算符创建新的 Font 对象,而是使用此类来获得所需的 Font。如果可能,该类可以具有重用现有字体的逻辑。或者将最后 10 种字体保留在内存中,并丢弃其他字体。为缓存实现IDisposable,它将在您的应用生命周期中调用一次。

      【讨论】:

      • 字体缓存类如何知道何时不再使用字体?您是否必须将字体“返回”到缓存中?
      • 是的,必须有一种方式告诉它您不再需要该字体。我通常使用某种实现IDisposableLease 类来执行此操作,并在Dispose() 方法中联系缓存并告诉它减少引用计数。同样的模式也可以用于工厂。
      • 那不是回到第一点吗?至少,尽管如此,您现在确实有一个管理器来管理您可能很昂贵的字体,但这确实引出了一个问题:为什么不应该假设构造函数/工厂和垃圾收集器在大多数情况下都无法完成任务的情况?
      • 不同之处在于您不必真正知道(或关心)资源会发生什么。工厂可以销毁它们或只是缓存它们以备后用。您可以稍后更改行为。您是否真的需要这种模式取决于您的资源有多昂贵以及您需要它们的频率。在复杂的 GDI 渲染的情况下,字体会被重复使用很多次(一次 Paint 调用会重复数千次),这肯定会加快速度。
      【解决方案5】:

      终结器被内置到类中,因为清理是必要的。无论您有大量或少量的对象要清理,清理它们都是一个好习惯。

      GC 被构建为拥有自己的伪思维。通过正确处理你的对象,你允许 GC 做它应该做的事情。

      但是,如果您要创建大量字体对象并处理所有字体对象,则每隔一段时间在适当的一代(可能是第 0 代)调用 GC 以自行启动 GC 清理可能会有所帮助,具体取决于关于您正在大量实例化的其他类型的对象。你的目标应该是让你知道你不会使用很长时间的物品不会被提升为老一代。这使 GC 的工作保持精益求精。

      只要用你最好的判断,你会没事的。但是,作为您的常规做法,请务必使用终结器处理任何对象。

      【讨论】:

        【解决方案6】:

        我至少运行了一个使用 .NET 运行时的其他应用程序。我不断收到 OutOfMemoryExceptions。最好让您的应用程序正常运行,以便其他应用程序在无法获得足够资源时不会引发异常。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-05
          • 2021-01-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多