如何解决.NET Webbrowser控件中的内存泄漏问题?
如何解决.NET Webbrowser控件中的内存泄漏问题?
这是一个众所周知的、古老的.NET Webbrowser控件问题。\n摘要:使用.NET webbrowser控件导航到页面会增加内存使用量,而这些内存永远不会被释放。\n复现内存泄漏:在窗体中添加一个WebBrowser控件。使用它导航到任何你想要的页面。about:blank可以,滚动到Google Images直到使用量超过100MB,然后浏览其他页面,你会发现几乎没有任何内存被释放,这是一个更加明显的演示。\n我目前的应用需求是长时间运行,显示一个限制的IE7浏览器窗口。不希望使用一些混合设置的IE7本身,尽管目前看来这是一个备选方案。\n将浏览器嵌入到Windows窗体应用程序中。\n对我来说,使用不同的浏览器基础是不可行的选择。我需要使用IE7。\n关于这个已知的内存泄漏的先前讨论和文章:\n
- \n
- http://www.vbforums.com/showthread.php?t=644658
- How to Fix the Memory Leak in IE WebBrowser Control?
- Memory leak when using WPF WebBrowser control in multiple windows
- 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/
\n
\n
\n
\n
\n
\n
\n经常提出但无效的解决办法:\n
- \n
- 浏览不同的页面并不重要。about:blank会触发内存泄漏。它不需要页面具有javascript或任何其他额外的技术。
- 使用不同版本的Internet Explorer没有关系。7、8和9都表现出相同的症状,据我所知,所有版本的控件都有相同的内存泄漏问题。
- Dispose()控件没有帮助。
- 垃圾回收也没有帮助。(事实上,我对此进行的研究表明,泄漏是Webbrowswer控件包装的非托管COM代码中的问题。)
- 最小化窗口并将进程可用内存设置为-1,-1(SetProcessWorkingSetSize()或类似方法)只能减少物理内存使用量,对虚拟内存没有影响。
- 调用WebBrowser.Stop()不是一个解决办法,并且会破坏使用除静态网页外的其他功能,而只是稍微减少内存泄漏。
- 在导航到另一个页面之前强制等待文档完全加载也没有帮助。
- 在单独的appDomain中加载控件无法解决这个问题。(我自己没有这样做过,但研究显示其他人在这条路上也没有成功。)
- 使用其他包装器如csexwb2也没有帮助,因为它也遭受同样的问题。
- 清除临时互联网文件缓存没有任何作用。问题出现在活动内存中,而不是磁盘上。
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n只有当整个应用程序关闭并重新启动时,内存才会被清除。\n如果这是一个确切解决问题的方法,我愿意直接使用COM或Windows API编写自己的浏览器控件。当然,我更希望有一个更简单的解决办法;我宁愿避免降到更低的层次去做事情,因为我不想在浏览器的支持功能方面重新发明轮子。更不要说在自己编写的浏览器中复制IE7的功能和非标准行为了。\n求助?
原因:在.NET WebBrowser控件中存在内存泄漏问题。
解决方法:
1. 使用反射和从mainForm的私有字段中删除引用的方式来清除内存泄漏。以下是代码示例:
//使用dispose方法清除大部分引用 this.webbrowser.Dispose(); BindingOperations.ClearAllBindings(this.webbrowser); //使用反射删除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); } } }
2. 对于WinForm应用程序,可以使用相同的解决方法。
在.NET Webbrowser控件中解决内存泄漏的方法是使用以下代码:
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; 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; }
这段代码的目的是在Dispose方法中解除对WebBrowser控件的引用,以防止内存泄漏。首先,通过调用_browser.Dispose()来释放WebBrowser控件的资源。然后,通过GetWindowElement方法获取与WebBrowser控件相关联的窗口引用。接下来,使用反射获取窗口的私有字段_swh,并进一步获取_sourceWindow和_keyboardInputSinkChildren字段的值。最后,通过遍历_keyboardInputSinkChildren集合,找到与_browser相等的当前站点,并调用Unregister方法取消注册。
在代码中,GetWindowElement方法用于获取与DependencyObject对象关联的窗口引用。它通过遍历可视树,找到第一个类型为Window的元素,并返回该元素。
感谢udione和其他人的帮助和代码贡献!
这个问题的出现原因是.NET Webbrowser控件中的一个内存泄漏,无论你在进程中做什么,都无法回收该内存。根据你的帖子,我可以看到你已经尝试了很多方法来避免内存泄漏,但都没有成功。
我建议采取不同的方法,如果可行的话。创建一个独立的应用程序,使用Webbrowser控件,并从你的应用程序中启动它。使用这里描述的方法将新创建的应用程序嵌入到你现有的应用程序中。使用WCF或.NET远程处理与该应用程序进行通信。定期重新启动子进程,以防止它占用过多的内存。
当然,这是一个相当复杂的解决方案,重新启动过程可能看起来很丑陋。也许你可以在用户导航到另一个页面时重新启动整个浏览器应用程序。
另外,WebBrowser
控件是UI的一部分吗?还是被用于网络自动化而被隐藏地使用?
在我的情况下,它是UI的一部分。
通过监视perfmon,我发现IE 11浏览器也会做类似的事情(可能是出于不同的原因)。它似乎会创建一个新的进程,将状态迁移到该进程,然后销毁原始进程。我们通过类似的技术“修复”了这个内存泄漏。