Winforms的WebBrowser是一个经常被使用的控件,用来浏览网页。可是很多程序员发现WebBrowser会有GDI Objects泄露。
一、 问题重现步骤
- 新建一个Winforms项目;
- 添加一个新Form,命名为FormWebBrowser;
- 在FormWebBrowser中添加一个WebBrowser,并为属性Url设置一个有效的网址;
- 回到Form1,在上面添加一个按钮;
- 双击按钮,进入代码编辑窗口;
- 为button1的Click添加事件处理器:
private void button1_Click(object sender, EventArgs e)
{
FormWebBrowser form = new FormWebBrowser();
form.ShowDialog();
}
- 编译运行;
- 打开任务管理器。如果进程(Processes)页面没有GDI Objects的列,点击菜单视图 (View)->选择列(Select Column…)。在弹出的对话框中,选择GDI Objects;
- 点击button1,这样FormWebBrowser就会弹出来。关掉FormWebBrowser,回到Form1。再点击button1。这样重复弹出FormWebBrowser再关掉。
- 在步骤9中,注意每次关掉FormWebBrowser时GDI Objects数目。我们会注意到GDI Objects数目会一直升高。这就是GDI Objects的症状。
二、 原因分析
为了正确显示网页的内容,WebBrowser会创建一些GDI的对象。GDI对象不是托管资源,因此当我们不再需要这些资源时,我们应该显示释放这些资源。WebBrowser提供了Dispose函数,在WebBrowser不再使用的时候,我们应该调用Dispose函数。
三、 建议
既然出现GDI Objects泄露的原因是没有调用WebBrowser.Dispose(),我们就应该在合适的时候调用这个方法。显然,我们在FormWebBrowser消失的时候,我们不会再使用WebBrowser,这个时候就可以调用该方法。也就是我们可以在FormWebBrowser的Closed事件处理器里调用WebBrowser.Dispose。
事实上Form也有Dispose方法。在Form.Dispose里,Winforms会去调用它所有子控件的Dispose方法。因此,一个更安全的办法就是当FormWebBrowser不再使用的时候,调用FormWebBrowser.Dispose。
C#提供了using的关键词。使用这个关键词创建的对象,.NET会确保在这些对象不再使用的时候会调用它们的Dispose。这样会比我们自己调用Dispose会更安全。
基于上面的分析,我们建议的办法是:我们用using关键词创建FormWebBrowser 对象。这样当我们关掉一个FormWebBrowser的时候,我们会确保它的Dispose会被调用。这样WebBrowser的Dispose也会被调用,GDI的资源也就随之释放,也就没有GDI的泄露了。
参考代码如下:
private void button1_Click(object sender, EventArgs e)
{
using (FormWebBrowser form = new FormWebBrowser())
form.ShowDialog();
}