Web 浏览器控件在处理单击事件时会泄漏内存
本文关键字:泄漏 内存 事件 单击 浏览器 控件 处理 Web | 更新日期: 2023-09-27 18:35:36
我在Windows 7,.NET 4.5,IE10中。 我有一个简单的窗口表单应用程序,带有WebBrowser
控件。每次页面导航时,我都想添加一个点击处理程序。我注意到当我这样做时内存泄漏。我实际上正在分离事件处理程序,因此对内存泄漏的原因感到困惑。
我注意到我可以附加到HtmlDocument.Click
事件,并且看不到与HtmlElement.Click
相关的泄漏。下面的程序演示了这一点,该程序使用计时器定期导航。
同样有趣的是,URL似乎确实很重要...例如,我看到 http://www.google.com 的问题,但没有看到 http://thin.npr.org(或关于:空白)的问题。这会让我相信,从本质上讲,这是一个 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 megs 私有字节和 640 个句柄。"泄漏"版本最多有数百个兆头和数千个手柄,而且似乎并没有放慢速度。在生产环境中,当事情停止运行时,用户有一个使用超过 900 meg 的进程(图像无法加载)。关闭应用程序需要几秒钟才能将所有内容拆掉。
行得通!关闭 Web 浏览器后,将立即释放句柄。使用代码:
using system.windows.forms;
[DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);
[DllImport("KERNEL32.DLL", EntryPoint = "GetCurrentProcess", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern IntPtr GetCurrentProcess();
// **Release handles invoking the above**
IntPtr pHandle = GetCurrentProcess();
SetProcessWorkingSetSize(pHandle, -1, -1);
祝您编码愉快!