我在Windows 7、.NET 4.5和IE10中使用一个简单的窗体应用程序,其中包含一个WebBrowser控件。每次页面导航时,我都想添加一个点击处理程序。我注意到这样做会导致内存泄漏。实际上,我正在分离事件处理程序,所以对于为什么会有内存泄漏感到困惑。
我注意到我可以连接到HtmlDocument.Click事件,并且不会看到与HtmlElement.Click相关的泄漏。下面的程序演示了这一点,它使用定时器定期导航。
另外值得注意的是,URL似乎很重要...例如,我在http://www.google.com中看到了问题,但在http://thin.npr.org(或about:blank)中没有看到。这会让我相信,本质上这是一个javascript问题,但是我当前使用的“修复”(如下所示)实际上与javascript无关...
那么是什么导致了问题,我该如何解决这些问题,使我的程序不会泄漏内存/句柄?
对于任何好奇的人,我使用了一个
在运行“已修复”版本一段时间后(可能是一个小时),它稳定在约40兆字节的私有字节和640个句柄。 “泄漏”的版本达到数百兆和数千个句柄,并且似乎没有放慢的迹象。在生产中,用户的进程使用超过900兆时,事情停止了(图像无法加载)。关闭应用程序需要几秒钟来撤销所有内容。
我注意到我可以连接到HtmlDocument.Click事件,并且不会看到与HtmlElement.Click相关的泄漏。下面的程序演示了这一点,它使用定时器定期导航。
另外值得注意的是,URL似乎很重要...例如,我在http://www.google.com中看到了问题,但在http://thin.npr.org(或about:blank)中没有看到。这会让我相信,本质上这是一个javascript问题,但是我当前使用的“修复”(如下所示)实际上与javascript无关...
那么是什么导致了问题,我该如何解决这些问题,使我的程序不会泄漏内存/句柄?
我的当前“解决方法”是在HtmlElement.DomElement
属性上调用Marshal.ReleaseComObject
。这样做是正确的吗?难道HtmlElement
不能像HtmlDocument
一样自动清理吗?除了HtmlElement
之外,我应该预计其他类也会出现问题吗?
我很想在任何看到com对象的地方慷慨地使用Marshal.ReleaseComObject
(并且不假设包含类将正确地这样做)。这样做安全吗?
我的完整程序如下:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public class Form1 : Form
{
Timer t;
WebBrowser webBrowser;
public Form1()
{
webBrowser = new WebBrowser
{
Dock = DockStyle.Fill,
Url = new Uri("http://www.google.com/")
};
webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
this.Controls.Add(webBrowser);
t = new Timer { Interval = 2000 };
t.Tick += t_Tick;
}
void t_Tick(object sender, EventArgs e)
{
t.Stop();
webBrowser.Navigate("http://www.google.com/");
}
void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser.ReadyState == WebBrowserReadyState.Complete)
{
var doc = webBrowser.Document;
if (doc == null) //no page loaded
return;
//the following two lines do not cause a leak
doc.Click += click_handler;
doc.Click -= click_handler;
var body = doc.Body;
if (body != null)
{
//the following 2 lines cause memory and handle leaks
body.Click += click_handler;
body.Click -= click_handler;
//uncommenting the following line appears to fix the leak
//Marshal.ReleaseComObject(body.DomElement);
body = null;
}
GC.Collect(2, GCCollectionMode.Forced);
t.Start();
}
}
void click_handler(object sender, HtmlElementEventArgs e)
{
}
}
}
对于任何好奇的人,我使用了一个
WeakReference
测试了我的程序中的body
对象,并验证了它正在被垃圾回收。在运行“已修复”版本一段时间后(可能是一个小时),它稳定在约40兆字节的私有字节和640个句柄。 “泄漏”的版本达到数百兆和数千个句柄,并且似乎没有放慢的迹象。在生产中,用户的进程使用超过900兆时,事情停止了(图像无法加载)。关闭应用程序需要几秒钟来撤销所有内容。
FEATURE_BROWSER_EMULATION
和FEATURE_MANAGE_SCRIPT_CIRCULAR_REFS
WebBrowser特性,看看是否有所改进(这只是一个想法)。 - noseratio - open to work