【问题标题】:Font causes GDI leak in custom control字体导致自定义控件中的 GDI 泄漏
【发布时间】:2013-04-22 03:12:38
【问题描述】:

我已经创建了一个如下所示的自定义控件。

public partial class TextBoxEx : TextBox
{
  public TextBoxEx()
  {
    InitializeComponent();
    Font = Utility.normalFont;
  }

   protected override void OnPaint(PaintEventArgs pe)
   {
    base.OnPaint(pe);
   }
 }
//A utility class to initialize font.   
class Utility
{

    internal static Font normalFont = new Font("Arial", 18);
}

我有两个表格 Form1 和 Form2。此 TextBoxEx 被添加到 Form2。单击 Form1 中的按钮时,我正在显示 Form2。

持续显示和关闭 Form2 会导致我的应用程序中的 GDI 泄漏。用GDI检测工具(Bear.exe)分析,发现是Font导致GDI泄漏。

我的问题是,

  1. 为什么调用 TextBoxEx 的 Dispose() 方法后字体没有释放。(关闭 Form2 时,会自动调用 TextBoxEx 的 Dispose() 方法)。
  2. 如何解决由字体引起的 GDI 泄漏? (TextBoxEx的Dispose()方法中不能调用Font.Dispose(),因为在构造函数中会抛出“Parameter is not valid”异常)。

【问题讨论】:

  • 字体不应该被释放,因为它在静态 normalFont 变量中被引用。我认为这根本不是泄漏..
  • @Alex:当我评论这行代码“Font = Utility.normalFont;”时,泄漏消失了。我认为字体保留副本而不是参考。我可以通过在 TextBoxEx 的 Dispose() 方法中设置 Font=null 来解决 GD 泄漏问题。但这是个好主意吗?
  • 字体是引用类型,所以不会被复制。我认为您的泄漏分析器提供了不正确的信息。阅读 Hans Passant 的回答,您在任何地方都找不到更好的专家 :)
  • @Alex:但是当我在 TextBoxEx 的 Dispose() 方法中设置 Font=null 时,GDI 泄漏消失了。我已经检查过 TaskManager。
  • 有趣。无论如何,在 Dispose 中将字体设置为 NULL 就可以了。

标签: c# winforms custom-controls gdi resource-leak


【解决方案1】:

大多数绘图对象的创建成本非常。例如,一支笔或刷子的创建时间不会超过一微秒。这就是为什么您应该始终在开始绘图时创建它们并在完成绘图时处理它们。 using 声明强烈建议您这样做。

然而,Font 类是困难的。创建它们便宜,Windows 需要做很多工作才能将您要求的字体映射到可用的字体集并加载 TrueType 轮廓。 Winforms 有一个解决方案,它缓存 字体。您将在第一次使用字体时产生创建字体的成本。但是您可以随后处理它,但字体对象仍保留在字体缓存中。下次创建相同字体时,您将从缓存中获得非常便宜的副本。

这也是 WPF 中的一个问题,尤其是因为它具有更丰富的字体支持,包括对 OpenType 轮廓的支持。它以不同的方式解决,WPF 使用完全独立的进程来缓存字体。充当任何 WPF 应用程序的字体缓存服务器。你会在任务管理器中看到这个进程,它是 PresentationFontCache.exe 进程。

Anyhoo,任何类型的泄漏诊断程序都会被这个缓存混淆。它会认为您的应用程序正在泄漏字体,它会看到存储在缓存中的字体。只有当使用的字体数量无限增长并最终导致程序崩溃时,您才会真正发生泄漏。易于测试,Windows 强加的配额很低,一个进程不能创建超过 10,000 个绘图对象。因此,如果您有真正的泄漏,您不需要运行您的测试程序很长时间即可达到该配额。您还可以在任务管理器中看到这一点。查看 + 选择列,勾选 GDI 对象复选框。确保您的测试程序的数量稳定,不超过几百,给予或接受。

【讨论】:

  • 当我不断打开和关闭窗体时,TaskManager 中的 GDI 计数不断增加 1。但是当我在 TextBoxEx 的 Dispose() 方法中设置 Font=null 时,GDI 泄漏消失了。是否建议在TextBox控件的Dispose()方法中设置Font为NULL?
  • 当我连续打开和关闭Form2时,TaskManager中的GDI计数一直在增加1。将GDI计数增加到某个计数(70-80)后,它会自动减少到某个计数,依此类推。当我将 500 TextBoxEx 添加到 form2 并不断打开和关闭 Form 时,TaskManager 中的 GDI 计数将为 504。它没有增加。在 TextBoxEx 的 Dispose() 方法中设置 Font=null 时,TaskManager 中的 GDI 计数将在关闭窗体时重置为 4。是否建议在 TextBox 控件的 Dispose() 方法中将 Font 设置为 NULL 还是应该忽略它并信任 GC 来收集 GDI 对象?
  • 我确实看到了类似的东西,很奇怪。但是它永远不会爆炸,GDI 对象计数不断下降和备份,而无需在 Dispose() 方法中执行任何操作或将字体引用设置回 null。有时间我会再挖一些,但你肯定没有问题。
  • 感谢您的回复。当我处理所有其他绘图对象(钢笔、画笔、位图等)时,我决定离开控件的 Font 对象并让 GC 收集它。无论如何,我会继续关注这个话题,如果你看到任何有趣的事情,请发帖。
猜你喜欢
  • 2012-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 2014-07-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多