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

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

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

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

我目前对应用程序的要求包括长时间运行,显示有限的IE7浏览器窗口。 运行IE7自己的一些混蛋设置的钩子,BHO和组策略也不希望,虽然这看起来像在这个时候的后备。 将浏览器embedded到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/

通常build议的修补程序不起作用:

  • 去不同的页面并不重要。 约:空白触发泄漏。 它不需要一个页面有JavaScript,或任何其他额外的技术。
  • 使用不同版本的Internet Explorer并不重要。 7,8,9都显示相同的症状,据我所知,所有版本都有相同的内存泄漏的控制。
  • 处置()控制不起作用。
  • 垃圾收集没有帮助。 (事实上​​,我已经做了这方面的研究表明泄漏是在Webbrowswer控制包裹的非托pipeCOM代码。)
  • 将进程可用内存最小化并设置为-1,-1(SetProcessWorkingSetSize()或simimlar。)只会减less物理内存使用量,不会影响虚拟内存。
  • 调用WebBrowser.Stop()不是一个解决scheme,并打破使用除静态网页以外的任何function,而不仅仅是稍微减less泄漏。
  • 在导航到另一个文档之前强制等待文档加载完全也无济于事。
  • 将控件加载到单独的appDomain中并不能解决问题。 (我自己并没有这样做,但研究显示其他人没有这个路线的成功。)
  • 使用不同的包装如csexwb2没有帮助,因为这也遭受同样的问题。
  • 清除临时Internet文件caching不做任何事情。 问题是在活动内存中,而不是在磁盘上。

整个应用程序closures并重新启动时,内存将被清除。

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

帮帮我?

这个泄漏似乎是在非托pipe内存泄漏,所以你在你的进程中没有任何东西会回收这个内存。 从你的文章中,我可以看到,你已经试图避免泄漏相当广泛,没有成功。

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

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

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

  1. IKeyboardInputSite是一个公共接口,具有方法Unregister() ,所以我们在收到对* _keyboardInputSinkChildren *集的引用后不需要使用reflection。

  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; } 

谢谢你们!

有一种方法可以通过使用reflection和从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); } } } 

在与WPF WebBrowser组件进行了不同方向( Win32 WorkingSet / COM SHDocVw接口等 )的精确内存不足问题后,我发现我们的问题是jqGrid插件保存在IE ActiveXHost中的非托pipe资源上,调用WebBrowser.Dispose() 。 这个问题通常是由不正确的Javascript创build的。 奇怪的是,在正常的IE浏览器工作正常 – 只是不从WebBrowser控制。 我猜测两个集成点之间的垃圾收集是不同的,因为IE永远不会被closures。

有一件事我build议,如果你正在编写源代码页是删除所有的JS组件,并慢慢添加回来。一旦你确定有问题的JS插件( 就像我们一样 ) – 应该很容易解决这个问题。 在我们的例子中,我们只使用了$("#jqgrid").jqGrid('GridDestroy')来正确地删除它创build的事件和相关的DOM元素。 当浏览器通过WebBrowser.InvokeScriptclosures时,通过调用这个来解决这个问题。

如果您无法修改您导航到的源页面,则必须在页面中注入一些JS来清理正在泄漏内存的DOM事件和元素。 如果微软发现了这个问题的解决scheme,那将是非常好的,但是现在我们仍然在探索需要清理的 JS插件。

下面的解决scheme为我工作:

 Protected Sub disposeBrowers() If debug Then debugTrace() If Me.InvokeRequired Then Me.Invoke(New simple(AddressOf disposeBrowers)) Else Dim webCliffNavigate As String = webCliff.Url.AbsoluteUri Me.DollarLogoutSub() If dollarLoggedIn Then Exit Sub End If 'Dim webdollarNavigate As String = webDollar.Url.AbsoluteUri Me.splContainerMain.SuspendLayout() Me.splCliffDwellers.Panel2.Controls.Remove(webCliff) Me.splDollars.Panel2.Controls.Remove(webDollar) RemoveHandler webCliff.DocumentCompleted, AddressOf webCliff_DocumentCompleted RemoveHandler webDollar.DocumentCompleted, AddressOf webDollar_DocumentCompleted RemoveHandler webCliff.GotFocus, AddressOf setDisposeEvent RemoveHandler webCliff.LostFocus, AddressOf setDisposeEvent RemoveHandler webDollar.GotFocus, AddressOf setDisposeEvent RemoveHandler webDollar.LostFocus, AddressOf setDisposeEvent webCliff.Stop() webDollar.Stop() Dim tmpWeb As SHDocVw.WebBrowser = webCliff.ActiveXInstance System.Runtime.InteropServices.Marshal.ReleaseComObject(tmpWeb) webCliff.Dispose() tmpWeb = webDollar.ActiveXInstance System.Runtime.InteropServices.Marshal.ReleaseComObject(tmpWeb) webDollar.Dispose() tmpWeb = Nothing webCliff = Nothing webDollar = Nothing GC.AddMemoryPressure(50000) GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForFullGCComplete() GC.Collect() GC.RemoveMemoryPressure(50000) webCliff = New WebBrowser() webDollar = New WebBrowser() webCliff.CausesValidation = False webCliff.Dock = DockStyle.Fill webDollar.CausesValidation = webCliff.CausesValidation webDollar.Dock = webCliff.Dock webDollar.ScriptErrorsSuppressed = True webDollar.Visible = True webCliff.Visible = True Me.splCliffDwellers.Panel2.Controls.Add(webCliff) Me.splDollars.Panel2.Controls.Add(webDollar) Me.splContainerMain.ResumeLayout() 'AddHandler webCliff.DocumentCompleted, AddressOf webCliff_DocumentCompleted 'AddHandler webDollar.DocumentCompleted, AddressOf webDollar_DocumentCompleted 'AddHandler webCliff.GotFocus, AddressOf setDisposeEvent 'AddHandler webCliff.LostFocus, AddressOf setDisposeEvent 'AddHandler webDollar.GotFocus, AddressOf setDisposeEvent 'AddHandler webDollar.LostFocus, AddressOf setDisposeEvent webCliff.Navigate(webCliffNavigate) disposeOfBrowsers = Now.AddMinutes(20) End If End Sub 

祝你好运,莱拉

我觉得这个问题已经很久没有回答了。 如此多的线程与相同的问题,但没有确凿的答案。

我已经find了解决这个问题的工作,并且想和大家分享所有仍然面临这个问题的人。

第1步:创build一个新的表单,比如form2,并在其上添加一个web浏览器控件。 第二步:在你有你的浏览器控件的form1中,只要删除它。 第3步:现在,转到Form2,并使此浏览器控件的访问修饰符为公共,以便可以在Form1中访问第4步:在form1中创build一个面板,并创buildform2的对象,并将其添加到面板。 Form2 frm = new Form2(); frm.TopLevel = false; frm.Show(); panel1.Controls.Add(FRM); 第五步:定期调用下面的代码frm.Controls.Remove(frm.webBrowser1); frm.Dispose();

而已。 现在,当你运行它,你可以看到加载的Web浏览器控件,它会定期处理,并没有更多的应用程序挂起。

您可以添加下面的代码,使其更有效。

  IntPtr pHandle = GetCurrentProcess(); SetProcessWorkingSetSize(pHandle, -1, -1); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); 

相信它更多的.Net框架问题,而不是Web浏览器控制。 用浏览器处理浏览器导航事件,然后导航到about:blank将是一个很好的解决方法。 例如:

 private void RemoveButton_Click(object sender, RoutedEventArgs e) { var browser = (WebBrowser) _stackPanel.Children[_stackPanel.Children.Count - 1]; _stackPanel.Children.RemoveAt(_stackPanel.Children.Count-1); NavigatedEventHandler dispose = null; dispose = (o, args) => { browser.Navigated -= dispose; browser.Dispose(); }; browser.Navigated += dispose; browser.Navigate(new Uri("about:blank")); } 

试试这个解决scheme,我知道它并不理想。 在每次页面加载后粘贴代码

 System.Diagnostics.Process loProcess = System.Diagnostics.Process.GetCurrentProcess(); try { loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet - 1); loProcess.MinWorkingSet = (IntPtr)((int)loProcess.MinWorkingSet - 1); } catch (System.Exception) { loProcess.MaxWorkingSet = (IntPtr)((int)1413120); loProcess.MinWorkingSet = (IntPtr)((int)204800); }