如何解决.NET Webbrowser控件中的内存泄漏问题?

这是.NET Web浏览器控件的一个广为人知的旧问题。

简介:拥有.NET webbrowser控件导航到页面会增加从不释放的内存使用量。

重现内存泄漏:将WebBrowser控件添加到窗体。 使用它来导航到任何你想要的页面。 关于:空白作品,在Google图片上滚动直到您的使用率为100MB +,然后在其他地方浏览以注意几乎没有任何内存被释放是一个更具戏剧性的演示。

我目前对应用程序的要求包括长时间运行,显示有限的IE7浏览器窗口。 运行IE7本身有一些混乱的钩子设置,BHO和组策略也不是所希望的,尽管目前这看起来像是后备。 将浏览器嵌入到Windows窗体应用程序中。 使用不同的浏览器基础不适合我。 IE7是必需的。

与此已知内存泄漏相关的以前的主题和文章:

  • http://www.vbforums.com/showthread.php?t=644658
  • 如何解决在IE WebBrowser控制内存泄漏?
  • 在多个窗口中使用WPF WebBrowser控件时发生内存泄漏
  • http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8/
  • http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/8a2efea4-5e75-4e3d-856f-b09a4e215ede
  • http://dotnetforum.net/topic/17400-appdomain-webbrowser-memory-leak/
  • 通常建议的修补程序不起作用:

  • 去不同的页面并不重要。 about:空白触发泄漏。 它不需要一个页面有JavaScript,或任何其他额外的技术。
  • 使用不同版本的Internet Explorer无关紧要。 7,8,9都显示相同的症状,据我所知,所有版本都有相同的控制内存泄漏。
  • 处置()控制不起作用。
  • 垃圾收集不起作用。 (事实上​​,我在这方面做的研究表明泄漏是在Webbrowswer控件包装的非托管COM代码中。)
  • 将进程可用内存最小化并设置为-1,-1(SetProcessWorkingSetSize()或simimlar。)只会减少物理内存使用量,不会影响虚拟内存。
  • 调用WebBrowser.Stop()不是一个解决方案,并且打破了除静态网页之外的任何其他功能,只是稍微减少了泄漏。
  • 强制等待文档在导航到另一个文档之前完全加载也无济于事。
  • 将控件加载到单独的appDomain中并不能解决问题。 (我自己并没有这样做,但研究表明其他人在这条路上没有成功。)
  • 使用诸如csexwb2之类的不同包装器不会有帮助,因为这也会遇到同样的问题。
  • 清除Temporary Internet Files缓存不会执行任何操作。 问题出在活动内存中,而不是在磁盘上。
  • 当整个应用程序关闭并重新启动时,内存被清除。

    如果这是对问题的肯定修复,我愿意直接在COM或Windows API中编写自己的浏览器控件。 当然,我宁愿不要那么复杂的修复; 我宁愿避免低级别做事情,因为我不想从浏览器支持的功能方面重新发明轮子。 Letalone在自己的样式浏览器中复制IE7功能和非标准行为。

    帮帮我?


    这个泄漏似乎是非托管内存中的泄漏,所以在你的进程中你没有做任何事情来回收这些内存。 从你的帖子中,我可以看到你已经尽力避免泄漏,并且没有成功。

    如果可行,我会建议采用不同的方法。 创建一个单独的应用程序,使用Web浏览器控制并从应用程序启动它。 使用此处描述的方法将新创建的应用程序嵌入到您自己的现有应用程序中。 使用WCF或.NET远程处理与该应用程序通信。 不时重新启动子进程,以防止进入太多内存。

    这当然是相当复杂的解决方案,重启过程可能看起来很丑。 每次用户导航到另一个页面时,您可能会诉诸重新启动整个浏览器应用程序。


    我拿了udione的代码(它对我有效,谢谢!),并改变了两件小事:

  • IKeyboardInputSite是一个公共接口,并且具有方法Unregister() ,因此我们在收到对* _keyboardInputSinkChildren *集合的引用后不需要使用反射。

  • 由于视图并不总是直接引用其窗口类(特别是在MVVM中),所以我添加了一个方法GetWindowElement(DependencyObject元素) ,它通过遍历可视树来返回所需的引用。

  • 谢谢,udione

    public void Dispose()
    {
        _browser.Dispose();
    
        var window = GetWindowElement(_browser);
    
        if (window == null)
            return;
    
        var field = typeof(Window).GetField("_swh", BindingFlags.NonPublic | BindingFlags.Instance);
    
        var valueSwh = field.GetValue(window);
        var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(valueSwh);
        var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(valueSourceWindow);
    
        var inputSites = valuekeyboardInput as IEnumerable<IKeyboardInputSite>;
    
        if (inputSites == null)
            return;
    
        var currentSite = inputSites.FirstOrDefault(s => ReferenceEquals(s.Sink, _browser));
    
        if (currentSite != null)
            currentSite.Unregister();
    }
    
    private static Window GetWindowElement(DependencyObject element)
    {
        while (element != null && !(element is Window))
        {
            element = VisualTreeHelper.GetParent(element);
        }
    
        return element as Window;
    }
    

    谢谢你们!


    有一种方法可以通过使用反射并从mainForm上的私有字段中删除引用来清除内存泄漏。 这不是一个好的解决方案,但对于绝望的人来说,这里是代码:

    //dispose to clear most of the references
    this.webbrowser.Dispose();
    BindingOperations.ClearAllBindings(this.webbrowser);
    
    //using reflection to remove one reference that was not removed with the dispose 
    var field = typeof(System.Windows.Window).GetField("_swh", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    
    var valueSwh = field.GetValue(mainwindow);
    
    var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSwh);
    
    var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSourceWindow);
    
    System.Collections.IList ilist = valuekeyboardInput as System.Collections.IList;
    
    lock(ilist)
    {
        for (int i = ilist.Count-1; i >= 0; i--)
        {
            var entry = ilist[i];
            var sinkObject = entry.GetType().GetField("_sink", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            if (object.ReferenceEquals(sinkObject.GetValue(entry), this.webbrowser.webBrowser))
            {
                ilist.Remove(entry);
            }
        }
    } 
    
    链接地址: http://www.djcxy.com/p/41831.html

    上一篇: How to get around the memory leak in the .NET Webbrowser control?

    下一篇: Binding Prevents Visio Events from Firing