谁能告诉我出了什么问题?
在删除了大量代码以发现我们的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;
我看到了两个潜在的问题:
-
如果你创建了一个实现
IDisposable
的对象实例,你必须调用Dispose方法,或者在using
块中封装它的使用。Form实现了IDisposable,所以你的代码应该像下面这样:using (Form myform = new Form()) { myform.Show(); } //frees resource by calling Dispose automatically
否则,您将看到这里看到的内存泄漏,因为您正在创建窗体的新实例,但此后您永远不会释放其资源。垃圾收集可能最终会为你在WinForms中释放Windows资源,因为终结器是在BCL中编写的,但是调用Dispose会立即完成。
-
你正在创建一个新的
Font
对象和分配它的字体属性,每次你的窗体初始化。这并不一定是坏事(设计师生成的代码大量地这样做),但是每个新的Font
实例占用一个GDI句柄,除非调用Font.Dispose,否则该句柄不会自动释放。我的猜测是你留下了另一个Font对象,它可能没有得到正确处置。如果您正在生成大量的这些对象,您可能希望以某种方式对其进行优化(例如通过共享一个Font实例)。在至少一种情况下,不对字体调用Dispose将导致此内存泄漏。
您正在使用的Font对象是GDI对象,并且在您处置它之前不会被处置。使用using语句使用Font对象,或者调用Font。处理FormClose事件