Как обойти утечку памяти в элементе управления .NET Webbrowser? - PullRequest
57 голосов
/ 29 ноября 2011

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

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

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

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

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

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

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

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

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

Справка?

Ответы [ 8 ]

12 голосов
/ 01 декабря 2011

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

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

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

7 голосов
/ 13 ноября 2012

Я взял код udione (он мне помог, спасибо!) И изменил две маленькие вещи:

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

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

Спасибо, Удионе

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

Спасибо всем!

4 голосов
/ 19 февраля 2012

Существует способ устранить утечки памяти, используя отражение и удаляя ссылки из приватных полей в 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);
        }
    }
} 
3 голосов
/ 13 марта 2013

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

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

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

1 голос
/ 09 мая 2013

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

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

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

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

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

        IntPtr pHandle = GetCurrentProcess();
        SetProcessWorkingSetSize(pHandle, -1, -1);


        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
1 голос
/ 21 февраля 2012

У меня сработало следующее решение:

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

Удачи, Layla

0 голосов
/ 07 ноября 2014

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

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

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