WebBrowser控件在处理点击事件时会特别泄漏内存。

3
我在Windows 7、.NET 4.5和IE10中使用一个简单的窗体应用程序,其中包含一个WebBrowser控件。每次页面导航时,我都想添加一个点击处理程序。我注意到这样做会导致内存泄漏。实际上,我正在分离事件处理程序,所以对于为什么会有内存泄漏感到困惑。
我注意到我可以连接到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兆时,事情停止了(图像无法加载)。关闭应用程序需要几秒钟来撤销所有内容。

1
尝试实现FEATURE_BROWSER_EMULATIONFEATURE_MANAGE_SCRIPT_CIRCULAR_REFSWebBrowser特性,看看是否有所改进(这只是一个想法)。 - noseratio - open to work
1
我实现了我能想到的每一个功能,但似乎没有任何效果!不过还是很感谢这些信息,它解决了一个完全无关的问题 :) - TCC
1
在问题中提到的“Marshal.ReleaseComObject”“修复”方案已经在生产中为多个用户工作了几天。我很想说这是一个已解决的问题...但感觉仍然不对劲。 - TCC
1个回答

0

这个可行!在关闭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);

编程愉快!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接