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

Azu*_*tor 57 .net browser memory memory-leaks webbrowser-control

这是.NET Webbrowser控件中一个众所周知的旧问题.

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

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

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

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

通常提出的修复不起作用:

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

关闭并重新启动整个应用程序时,将清除内存.

我愿意直接在COM或Windows API中编写自己的浏览器控件,如果这是对问题的肯定修复.当然,我更喜欢一个不太复杂的修复; 我宁愿避免降低级别来做事情,因为我不想在浏览器支持的功能方面重新发明轮子.让我们在自己动手的浏览器中复制IE7功能和非标准行为.

救命?

Iva*_*van 12

这种泄漏似乎是非托管内存中的泄漏,因此您在进程中所做的任何事情都不会回收该内存.从你的帖子我可以看到你已经尝试避免泄漏相当广泛但没有成功.

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

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


Ser*_*kov 7

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

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

  2. 由于视图并不总是直接引用其窗口类(特别是在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;
}
Run Code Online (Sandbox Code Playgroud)

谢谢你们!


小智 5

有一种方法可以通过使用反射并删除 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);
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • @udione,WinForm应用程序该怎么办? (4认同)
  • 似乎与此 WPF 论坛线程中的解决方案相同:http://social.msdn.microsoft.com/Forums/en/wpf/thread/368755dd-0047-4a88-9951-ba0539266410 (3认同)