可能的错误与.net winforms,或者我只是错过了什么?GDI对象泄漏



谁能告诉我出了什么问题?

在删除了大量代码以发现我们的GDI对象泄漏(使用任务管理器并看着"GDI对象"列增长到10,000并且我们的应用程序崩溃)之后,我将代码减少到只有。net代码,没有任何自定义业务代码。我们仍然遇到这个问题。

我创建了一个测试应用程序来复制这个问题,它具有以下基本行为:

  • 打开表单150次(150没有什么特别的,只是一个足够大的数字,很容易看到"卡住"的手柄)。窗体上的计时器将关闭窗体1秒后
  • 运行垃圾回收器(不是真正必要的,但可以帮助摆脱"好"或"工作"对象不是问题的一部分)
  • 手动观察应用程序的GDI对象计数(您应该在打开表单150次之前和之后进行此操作)在我运行测试之前,我通常得到36的计数,在测试之后,它大约是190。每次我运行测试,这个计数都会增加大约150。

现在这个被启动了150次的表单以一种特定的方式进行了设置(我们称这个表单为"BadForm")。这是一个静态数据表,绑定到表单上的一个组合框。

BadForm有一个comboBox和一个计时器。下面是这个表单的代码:

using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
namespace GDIObjectLeakTest
{
  public partial class MyForm :Form
  {
    public static DataTable CachedNodeType = new DataTable();
    public MyForm()
    {
      InitializeComponent();
      this.comboBox1.SelectedIndexChanged += new EventHandler(this.comboBox1_SelectedIndexChanged);
      this.Font = new Font("Modern No. 20", 8.249999F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0))); ;
      comboBox1.DataSource = CachedNodeType;
    }
    private void timer1_Tick(object sender, EventArgs e)
    {
      Close();
    }
    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    { }
  }
}   

下面是运行测试的应用程序的主要形式的代码。上面有两个按钮。Button1运行BadForm 150次。按钮2运行垃圾收集器100次(一次或两次对我来说不够好,我猜)(我使用垃圾收集器只是为了证明有/没有问题)。

private void button1_Click(object sender, EventArgs e)
{
  try
  {
    for(int i = 0; i < 150; i++)
    {
      //new SearchForm().Show();
      new MyForm().Show();
    }
  } catch(Exception ee)
  {
    throw;
  }
}
private void button2_Click(object sender, EventArgs e)
{
  for(int i = 0; i < 100; i++)
  {
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

尝试将此添加到dispose方法的顶部(在设计器文件中):

comboBox1.DataSource = null;

我看到了两个潜在的问题:

  1. 如果你创建了一个实现IDisposable的对象实例,你必须调用Dispose方法,或者在using块中封装它的使用。Form实现了IDisposable,所以你的代码应该像下面这样:

        using (Form myform = new Form())
        {
            myform.Show();
        }   //frees resource by calling Dispose automatically
    

    否则,您将看到这里看到的内存泄漏,因为您正在创建窗体的新实例,但此后您永远不会释放其资源。垃圾收集可能最终会为你在WinForms中释放Windows资源,因为终结器是在BCL中编写的,但是调用Dispose会立即完成。

  2. 你正在创建一个新的Font对象和分配它的字体属性,每次你的窗体初始化。这并不一定是坏事(设计师生成的代码大量地这样做),但是每个新的Font实例占用一个GDI句柄,除非调用Font.Dispose,否则该句柄不会自动释放。我的猜测是你留下了另一个Font对象,它可能没有得到正确处置。如果您正在生成大量的这些对象,您可能希望以某种方式对其进行优化(例如通过共享一个Font实例)。在至少一种情况下,不对字体调用Dispose将导致此内存泄漏。

您正在使用的Font对象是GDI对象,并且在您处置它之前不会被处置。使用using语句使用Font对象,或者调用Font。处理FormClose事件

相关内容

最新更新