C# 字体导致自定义控件中的GDI泄漏

C# 字体导致自定义控件中的GDI泄漏,c#,winforms,custom-controls,gdi,resource-leak,C#,Winforms,Custom Controls,Gdi,Resource Leak,我创建了一个自定义控件,如下所示 public partial class TextBoxEx : TextBox { public TextBoxEx() { InitializeComponent(); Font = Utility.normalFont; } protected override void OnPaint(PaintEventArgs pe) { base.OnPaint(pe); } } //A utility c

我创建了一个自定义控件,如下所示

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);
}
我有两张表格,表格一和表格二。此TextBoxEx已添加到Form2中。单击表单1中的按钮时,我正在显示表单2

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

我的问题是,

  • 为什么即使调用了TextBoxEx的Dispose()方法也不释放字体(关闭Form2时,会自动调用TextBoxEx的Dispose()方法)
  • 如何解决字体导致的GDI泄漏?。 (无法在TextBoxEx的Dispose()方法中调用Font.Dispose(),因为它在构造函数中引发“参数无效”异常)
  • 大多数图形对象的创建成本都很低。例如,一支笔或画笔的创建时间不会超过一微秒。这就是为什么在开始绘制时应始终创建它们,并在完成绘制时进行处理。using语句强烈鼓励您这样做

    但是字体类很难。创建它们并不便宜,Windows需要做大量工作来将您要求的字体映射到可用的字体集,并加载TrueType大纲。Winforms有一个解决方案,它缓存字体。第一次使用字体时,您将承担创建字体的费用。但是您可以处理它,但字体对象仍保留在字体缓存中。下次创建相同字体时,您将从缓存中获得非常便宜的副本

    这在WPF中也是一个问题,因为它有更丰富的字体支持,包括对OpenType大纲的支持。这是另一种解决方法,WPF使用一个完全独立的过程来缓存字体。充当任何WPF应用程序的字体缓存服务器。您将在任务管理器中看到此过程,它是PresentationFontCache.exe过程


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

    Font不应释放,因为它在静态normalFont变量中引用。我不认为这是一个漏洞。@Alex:当我对这行代码“Font=Utility.normalFont;”进行注释时,漏洞就消失了。我认为这种字体保存的是一份副本,而不是参考资料。我可以通过在TextBoxEx的Dispose()方法中设置Font=null来解决GD泄漏问题。但这是一个好主意吗?字体是引用类型,所以不会被复制。我认为你的泄漏分析器提供了错误的信息。阅读Hans Passant的回答,你在任何地方都找不到更好的专家:)@Alex:但当我在TextBoxEx的Dispose()方法中设置Font=null时,GDI泄漏消失了。我已经和TaskManager联系过了。很有趣。无论如何,在Dispose中将字体设置为NULL是可以的。当我连续打开和关闭表单时,TaskManager中的GDI计数会不断增加1。但当我在TextBoxEx的Dispose()方法中设置Font=null时,GDI泄漏消失。是否建议在TextBox控件的Dispose()方法中将字体设置为NULL?当我连续打开和关闭Form2时,TaskManager中的GDI计数会不断增加1。将GDI计数增加到某个计数(70-80)后,它将自动减少到某个计数,依此类推。当我向form2中添加500 TextBoxEx并持续打开和关闭表单时,TaskManager中的GDI计数将为504。它没有增加。在TextBoxEx的Dispose()方法中设置Font=null时,TaskManager中的GDI计数将在关闭表单时重置为4。是建议在TextBox控件的Dispose()方法中将字体设置为NULL,还是应该忽略它并信任GC收集GDI对象?我确实看到了类似的奇怪现象。但是,它不会爆炸,GDI对象计数会不断下降并备份,而无需在Dispose()方法中执行任何操作或将字体引用设置回null。等我有时间的时候,我会再挖一些,不过你肯定没问题。谢谢你的回复。当我处理所有其他绘图对象(钢笔、画笔、位图等)时,我决定离开控件的字体对象,让GC来收集它。无论如何,我会继续关注这个帖子,如果你看到任何有趣的事情,我会发帖子。