Как обойти утечку памяти в элементе управления .NET Webbrowser?

Это широко известная старая проблема с элементом управления .NET Webbrowser.

Резюме: наличие элемента управления веб-браузером .NET. Переход на страницу увеличивает использование памяти, которая никогда не освобождается.

Воспроизведите утечку памяти: добавьте элемент управления WebBrowser в форму. Используйте его для перехода на любые страницы, которые вы хотите. о: пустые работы, прокрутка на Картах Google до тех пор, пока ваше использование не станет 100 Мбайт +, а затем просмотр в другом месте, чтобы заметить, что только что освобожденная память является более драматичной демонстрацией.

Мои текущие требования к приложению include запуск его в течение длительных периодов времени, отображение ограниченного windows браузера IE7. Запуск IE7 сам по себе с некоторыми ублюдками настройки крючков, BHOs ​​и групповые политики также не желательны, хотя это выглядит как резерв в это время. Внедрение браузера в приложение Windows Forms. Использование другой базы браузеров не является доступным вариантом для меня. Требуется IE7.

Предыдущие темы и статьи, относящиеся к этой известной утечке памяти:

  • http://www.vbforums.com/showthread.php?t=644658
  • Как исправить утечку памяти в IE WebBrowser Control?
  • Утечка памяти при использовании элемента управления WPF WebBrowser в нескольких 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/

Часто предлагаемые исправления, которые НЕ РАБОТАЮТ:

  • Переход на разные страницы не имеет значения. about: blank запускает утечку. Это не требует, чтобы страница имела javascript или любую другую дополнительную технологию.
  • Использование разных версий Internet Explorer не имеет значения. 7, 8 и 9 имеют одинаковые симптомы, и, насколько я слышал, все версии имеют такую ​​же утечку памяти в элементе управления.
  • Dispose () управление не помогает.
  • Сбор мусора не помогает. (На самом деле, исследования, которые я сделал в этом, указывают на то, что утечка находится в неуправляемом COM-коде, который управляет Webbrowswer.)
  • Минимизация и настройка доступной памяти процесса до -1, -1 (SetProcessWorkingSetSize () или simimlar.) Только уменьшает использование физической памяти, не влияет на виртуальную память.
  • Вызов WebBrowser.Stop () не является решением и нарушает функциональность для использования чего-либо, кроме статических веб-страниц, не делая больше, чем просто минимизирует утечку.
  • Принуждение к загрузке документа полностью, прежде чем переходить на другой также не поможет.
  • Загрузка элемента управления в отдельный appDomain не устраняет проблему. (Я не делал этого сам, но исследования показывают, что другие не добились успеха в этом маршруте.)
  • Использование другой оболочки, такой как csexwb2, не помогает, так как это также страдает от одной и той же проблемы.
  • Очистка кеша временных интернет-файлов ничего не делает. Проблема в активной памяти, а не на диске.

Память очищается, когда все приложение закрывается и перезапускается.

Я готов напрямую написать собственный контроль браузера в COM или Windows API, если это окончательное решение проблемы. Конечно, я бы предпочел менее сложное решение; Я бы предпочел не спускаться на более низкие уровни, чтобы что-то делать, потому что я не хочу изобретать колесо с точки зрения поддерживаемых функций браузера. Letalone дублирует функции IE7 и нестандартное поведение в браузере с рулонами.

Помогите?

    Эта утечка, по-видимому, является утечкой в ​​неуправляемой памяти, поэтому ничто из того, что вы делаете в своем процессе, не будет исправлять эту память. С вашего поста я вижу, что вы попытались избежать утечки довольно широко и без успеха.

    Я бы предложил другой подход, если это возможно. Создайте отдельное приложение, которое использует элемент управления веб-браузера и запускает его из вашего приложения. Используйте описанный здесь метод для внедрения вновь созданного приложения в ваше собственное существующее приложение. Общайтесь с этим приложением, используя удаленные WCF или .NET. Периодически перезагружайте дочерний процесс, чтобы не допустить его большого объема памяти.

    Это, конечно, довольно сложное решение, и процесс перезапуска, вероятно, может выглядеть уродливым. Возможно, вам придется прибегнуть к перезапуску всего приложения браузера каждый раз, когда пользователь перейдет на другую страницу.

    Я взял код udione (он работал для меня, спасибо!) И изменил две мелочи:

    1. IKeyboardInputSite – это открытый интерфейс и имеет метод Unregister () , поэтому нам не нужно использовать reflection после того, как мы получили ссылку на коллекцию * _keyboardInputSinkChildren *.

    2. Поскольку представление не всегда имеет прямую ссылку на его class windows (особенно в MVVM), я добавил метод GetWindowElement (DependencyObject element), который возвращает требуемую ссылку, пройдя через визуальное дерево.

    Спасибо, 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; 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); } } } 

    После того, как я столкнулся с этой проблемой « Исходя из памяти» с разных сторон ( интерфейсы Win32 WorkSet / COM SHDocVw и т. Д. ) WebBrowser компонентом WPF WebBrowser , я обнаружил, что проблема для нас была в том, что плагин jqGrid, удерживающий на неуправляемых ресурсах в IE ActiveXHost и не освобождающий их после вызов WebBrowser.Dispose() . Эта проблема часто создается Javascript, который не ведет себя правильно. Странно, что Javascript отлично работает в обычном IE – просто не изнутри WebBrowser управления WebBrowser . Я предполагаю, что garbage collection отличается между двумя точками интеграции, поскольку IE никогда не может быть действительно закрыт.

    Одна вещь, которую я предлагаю, если вы создаете исходные страницы, – это удалить все компоненты JS и медленно добавлять их обратно. После того, как вы идентифицируете нарушающий JS-плагин ( как и мы ), должно быть легко устранить проблему. В нашем случае мы просто использовали $("#jqgrid").jqGrid('GridDestroy') чтобы правильно удалить события и связанные с ними элементы DOM. Это позаботилось об этой проблеме, вызвав ее, когда браузер закрыт через WebBrowser.InvokeScript .

    Если у вас нет возможности изменять исходные страницы, на которые вы переходите, вам нужно будет добавить несколько JS на страницу, чтобы очистить события и элементы DOM, которые вызывают утечку памяти. Было бы неплохо, если бы Microsoft нашла разрешение на это, но пока мы остаемся исследовать плагины JS, которые нуждаются в очистке .

    Решение ниже помогло мне:

     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 

    Удачи, Лейла

    Я думаю, что этот вопрос давно остался без ответа. Так много streamов с тем же вопросом, но не с окончательным ответом.

    Я нашел работу по этой проблеме и хотел поделиться с вами всеми, кто все еще сталкивается с этой проблемой.

    step1: Создайте новую форму say form2 и добавьте в нее элемент управления веб-браузером. step2: В форме1, где у вас есть элемент управления веб-браузером, просто удалите его. step3: Теперь перейдите в Form2 и сделайте модификатор доступа для этого элемента управления веб-браузером общедоступным, чтобы его можно было получить в Form1 step4: создать панель в форме1 и создать объект формы2 и добавить его в панель. Form2 frm = new Form2 (); frm.TopLevel = false; frm.Show (); panel1.Controls.Add (FRM); step5: Вызвать код ниже с регулярными интервалами frm.Controls.Remove (frm.webBrowser1); frm.Dispose ();

    Это оно. Теперь, когда вы запустите его, вы можете увидеть, что управление веб-браузером загружено, и оно будет установлено через равные промежутки времени, и больше не будет висит приложение.

    Вы можете добавить приведенный ниже код, чтобы сделать его более эффективным.

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

    Поверьте, это скорее проблема .NET Framework, а не управление веб-браузером. Привязка к навигационному событию браузера с помощью обработчика, который будет располагать браузер, а затем перейти к: 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")); } 

    Попробуйте это решение, я знаю, что он не идеален. Вставить код после загрузки каждой страницы

     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); } 
    Давайте будем гением компьютера.