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 浏览器控件在处理单击事件时会泄漏内存

行得通!关闭 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);

祝您编码愉快!