Как исправить утечку памяти в элементе управления IE WebBrowser?

Я пытаюсь встроить элемент управления WebBrowser в приложение C# Winform. Это звучит достаточно просто. Однако я обнаружил, что элемент управления WebBrowser поглощает много памяти каждый раз, когда я вызываю метод Navigate. Память никогда не освобождается. Использование памяти растет и растет...

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

Утечка памяти в элементе управления IE WebBrowser

Один человек предложил обновить до IE8, чтобы исправить проблему.

Однако мне нужно решение, которое работает независимо от того, установлена ​​ли у пользователя последняя версия IE. У меня нет контроля над пользовательской средой.

Кто-нибудь знает, как освободить память, занятую элементом управления WebBrowser? Есть ли обходные пути? Существуют ли альтернативы элементу управления WebBrowser?

Обновление: я только что сделал еще несколько тестов. На работе я работаю под управлением Windows XP и IE6. Память там не растет. Память увеличивается при вызове метода навигации, но освобождается через некоторое время. Дома я использую Vista и обновился до IE8. Здесь я тоже не вижу проблемы больше. Похоже, что проблема специфична для IE7. Поэтому вопрос следует перефразировать так: "Как исправить утечку памяти в IE WebBrowser Control, когда установлен IE7". Кто-нибудь может подтвердить, что эта проблема специфична для IE7?

18 ответов

Мое приложение также постоянно потребляло память при навигации и больше не выпускало. Я нашел решение для меня здесь: http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8

Для полноты изложения пост приведу заметную выдержку:

-- in class definition

    [DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);

    [DllImport("KERNEL32.DLL", EntryPoint = "GetCurrentProcess", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    internal static extern IntPtr GetCurrentProcess();

- код для вызова, когда вы хотите уменьшить память

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

все почести: http://social.msdn.microsoft.com/profile/mike_t2e/?type=forum&referrer=http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8 для публикации решения.

и http://ict-engineer.blogspot.com/2010/10/net-webbrowser-control-memory-leak.html для SEO'а, как надо, чтобы я мог его найти;)

Привет

редактировать: если это поможет вам быстро решить проблему - хорошо. но вы должны переборщить свой дизайн приложения, шаблон, который вы используете, если таковой имеется, рефакторировать объект, если вы будете использовать его гораздо дольше...

ОСНОВНАЯ ИДЕЯ,

"Убей себя и возродись".

Windows решит все проблемы с памятью.

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

Итак, НАЧНИТЕ НОВУЮ, и ЗАКРЫТЬ СТАРУЮ.

Сначала включите новый и выключите старый.


public void SOLVE_ALL_MY_MEMORY_PROBLEM()
{
  System.Diagnostics.Process.Start("MyProgram.exe");
  Application.Exit();
}

https://www.youtube.com/watch?v=aTBlKRzNf74

Если есть параметр,

public void SOLVE_ALL_MY_MEMORY_PROBLEM()
{
  System.Diagnostics.Process.Start("MyProgram.exe", "PARA_para_dance");
  Application.Exit();
}

Перейти к Program.cs

    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        if(args.Count() > 0)
            Application.Run(new Form1(args[0]));
        else
            Application.Run(new Form1());
    }

и перейдите к Form1.cs и сделайте еще один Form1()

    public Form1()
    {
        InitializeComponent();
    }

    public Form1(string dance_name)
    {
        InitializeComponent();

        ...
    }

или вы можете использовать временный файл!!!

Я только что создал простое приложение с контролем веб-браузера, чтобы попытаться повторить ваши результаты. Я обнаружил, что да, каждый раз, когда вы переходите на страницу, используемая память значительно увеличивается. ОДНАКО, это НЕ утечка памяти, потому что если вы продолжите навигацию, вы увидите, что через некоторое время память значительно падает, что указывает на то, что сборщик мусора сделал свое дело. Чтобы доказать это, я заставил сборщик мусора собирать данные после каждого вызова Navigate, и общий объем используемой памяти оставался на прежнем уровне после каждого вызова навигации.

Таким образом, несмотря на то, что он НАБИРАЕТ память каждый раз, когда вы "переходите", это НЕ утечка памяти, и вы освободите память. Если он набирает слишком быстро, просто вызовите GC.Collect();

Согласно MSDN, элемент управления System.Windows.Forms.WebBrowser является управляемой оболочкой для элемента управления ActiveX WebBrowser и использует любую версию элемента управления, установленную на компьютере пользователя.

Вы можете найти метод Dispose(bool) в метаданных класса WebBrowser (нажмите F12 в Visual Stuio) для освобождения неуправляемого ресурса.(NOT Dispose())

Код здесь

protected override void Dispose(bool disposing) {
    if (disposing) {
        if (htmlShimManager != null)
        {
            htmlShimManager.Dispose();
        }
        DetachSink();
        ActiveXSite.Dispose();
    }
    base.Dispose(disposing);
}

Но если вы попытаетесь вызвать WebBrowser.Dispose(bool), появится ошибка компилятора CS1540.

Класс WebBrowser поддерживает метод Dispose(bool), НО мы не можем его использовать.
Я думаю, что класс WebBrowser был разработан неправильно.

У меня есть идея вызвать WebBrowser.Dispose (true).
ОЧЕНЬ ПРОСТО! но это не очень хороший способ

Пример кода здесь (нужно 3 кнопки и 1 TextBox)

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Test_20170308_01
{
    public partial class Form1 : Form
    {
        [DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize);
        public static void FlushMemory()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
            }
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void addWeb()
        {
            WebBrowserD webBrowser1 = new WebBrowserD();
            webBrowser1.Size = new Size(1070, 585);
            this.Controls.Add(webBrowser1);
            webBrowser1.Navigate("about:blank");
        }

        private void RemoveWeb()
        {
            foreach (Control ctrl in this.Controls)
            {
                if (ctrl is  WebBrowserD)
                {
                    WebBrowserD web = (WebBrowserD)ctrl;
                    this.Controls.Remove(ctrl);
                    web.Navigate("about:blank");
                    web.Dispose(true);
                    FlushMemory();
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            addWeb();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            RemoveWeb();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            foreach (Control ctrl in this.Controls)
            {
                if (ctrl is WebBrowserD)
                {
                    WebBrowserD axweb = (WebBrowserD)ctrl;
                    axweb.Navigate(textBox1.Text);
                    FlushMemory();
                }
            }
        }
    }

    public class WebBrowserD : WebBrowser
    {
        internal void Dispose(bool disposing)
        {
            // call WebBrower.Dispose(bool)
            base.Dispose(disposing);
        }
    }
}

Этот код может предотвратить утечку памяти.

Таким образом, вам нужен только один класс.

    public class WebBrowserD : WebBrowser
    {
        internal void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
    }

Есть альтернативный элемент управления, который использует Gecko (движок Firefox использует) вместо Trident и очень хорошо работает с интерфейсами MSHTML.

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

Недостатком является то, что вам нужно будет поставлять Gecko вместе с вашим приложением, в последний раз я использовал эквивалент Firefox 2, и он занимал около 8 МБ.

Некоторое время назад я выпустил приложение, в котором сравнивали рендеринг IE и Firefox друг с другом, и оба они обновлялись по мере того, как вы редактировали CSS. У меня не было проблем с памятью, которые у вас были с веб-браузером, но я нашел, что с Gecko очень легко работать. Он не имеет того же класса управляемых оболочек, что и элемент управления.net WebBrowser, но его достаточно легко обойти.

Кажется, что метод Navigate() сохраняет все посещенные страницы в памяти, так как вы можете использовать метод GoBack (), на самом деле "утечки памяти" нет. Моя программа посещает один и тот же URL несколько раз. Проблема "утечки памяти" может быть устранена с помощью метода Refresh () вместо метода Navigate(), за которым следует GC.Collect(). Кодекс в следующем:

       try
        {
            if (webBrowser.Url.Equals("about:blank")) //first visit
            {
                webBrowser.Navigate(new Uri("http://url"));
            }
            else
            {
                webBrowser.Refresh(WebBrowserRefreshOption.Completely);
            }
        }
        catch (System.UriFormatException)
        {
            return;
        }
        System.GC.Collect(); // may be omitted, Windows can do this automatically

Я просмотрел весь интернет и не смог найти ответ на эту проблему. Я исправил это, используя ниже:

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

        '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()

        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()

        'vb.net for some reason automatically recreates these and the below is not needed
        '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)
        'webDollar.Navigate(webdollarNavigate)
        disposeOfBrowsers = Now.AddMinutes(20)
    End If
End Sub

Я знаю, что это не самое красивое или идеальное решение, но оно мне очень помогло. - Лейла

В элементе управления WebBrowser есть известная утечка памяти. См. Следующую статью Microsoft KB - KB893629.

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

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

Шаг 1. Создайте новую форму, скажем, form2, и добавьте в нее элемент управления веб-браузера. Шаг 2: В форме 1, где у вас есть элемент управления веб-браузера, просто удалите его. Шаг 3: Теперь перейдите к Form2 и сделайте модификатор доступа для этого элемента управления веб-обозревателем общедоступным, чтобы к нему можно было получить доступ в Form1. Шаг 4. Создайте панель в form1 и создайте объект form2 и добавьте его в панель. Form2 frm = новая 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();

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

Public Class MyApplication

    Private _AppTimer As Timers.Timer

    Public Sub New()
        _AppTimer = New Timers.Timer()
        _AppTimer.Interval = 1 * 60 * 60 * 1000 '1 Hour * 60 Min * 60 Sec * 1000 Milli

        AddHandler _AppTimer.Elapsed, AddressOf AppTimer_Elapsed

        _AppTimer.Start()
    End Sub

    Private Sub AppTimer_Elapsed(s As Object, e As Timers.ElapsedEventArgs)
        Application.Restart()
    End Sub

End Class

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

Я столкнулся с этой проблемой при простом управлении веб-браузером в форме. Он перешел на одну веб-страницу и остался там. По словам диспетчера задач, за 10 минут он съел 2 Гб памяти из 120 Мб.

Простым решением для моего проекта было перейти к свойствам элементов управления веб-браузера в Visual Studio и изменить значение параметра "AllowNavigation" на false. Теперь, когда я запускаю свою программу, она остается на 120 МБ. Я надеюсь, что это помогает кому-то!

Это почти конец 2017 года, и все же эта досадная ошибка присутствует в WebBrowser.

Я перепробовал все решения здесь, и ни одно из них не помогло мне. Утечка памяти все еще сохраняется... Самое странное, что когда я звоню:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

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

Это действительно уменьшает память много! но когда вызывается следующая инструкция Navigate, вся просочившаяся память возвращается в область видимости.... например (если объем памяти составляет 450 МБ.. эта инструкция уменьшается примерно до 20 МБ и сразу после повторного вызова.Navigate(string) она переходит на 460 МБ и утечка памяти продолжается...

Я даже пытался использовать Dispose(), переходя к about:blank перед следующей страницей, устанавливая объект webbrowser в null и создавая новый. Все эти попытки приводят к утечке памяти... это действительно расстраивает... Какие-нибудь другие решения?

У меня были такие же похожие проблемы. Я отправил более 5000 запросов навигации через веб-браузер, чтобы очистить динамические страницы. Приблизительно после 50 запросов мне не хватило бы памяти, так как использование памяти для навигационных запросов не освобождается после каждого запроса. Я использовал webBrowser.Dispose() после навигации, и это решило проблему. Это не связано с IE7 или около того. Я использую IE 11 и получил ту же проблему. Это потому, что я не выбрасывал навигационные объекты. Надеюсь это поможет.

Просто объявите элемент управления WebBrowser, используя ключевое слово "using". Это прекратит утечку памяти при вызове метода Navigate() больше. Я только что проверил это, и он работал хорошо для меня.

using (var webBrowser = new System.Windows.Forms.WebBrowser())
{
    webBrowser.Navigate(url);
}

Я столкнулся с той же проблемой, в качестве альтернативы, вместо перехода на новую страницу, я просто переписал ту же HTML-страницу, используя объект system.oi.streamreader/writer и вызвав обновление. Очевидно, что это не сработает в ситуации, когда контент для браузера подается онлайн, но для меня это помогает.

Кроме того, в настоящее время я использую более 8 элементов управления браузером, все активные одновременно, чтобы предоставлять отчеты через javascript внутри моего приложения.net. Когда пользователь активирует один браузер, html, на который указывают другие браузеры, очищается, а браузеры обновляются. Имея 8 браузеров, работающих с этими двумя методами, я могу легко держать свое приложение под управлением памяти Firefox, открыв всего 3 вкладки.

Я перепробовал все решения, описанные выше, но даже одно не помогло (кроме перезапуска программы, что не является решением).

Затем я сдаюсь так: "Хорошо, если вы хотите так много памяти, то вы получите его и переключите мое приложение на x64". Вы уже знаете, что случилось?

Похоже, что нет утечки памяти в 64-разрядной версии этой библиотеки. Я посмотрел в диспетчере задач, и мое приложение никогда не превышало 300 МБ, в то время как в x86 оно съело 1,4 ГБ, а затем перестало работать.

Так что... может быть, эта информация поможет вам.

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

Это сработало для меня, не уверен, что если 100% очищает память, кажется, что он все еще сохраняет кэшированные страницы, но больше не использует 500 МБ памяти, больше 60 МБ.

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

string eventBuffer;

void GetContracts_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            var web = sender as WebBrowser;
            if (web.Url == e.Url)
            {
                TaskMaster.Get_Contracts(ref web);
                if(Memory.Contracts.Count==0)
                {
                    eventBuffer="UpdateContractFailed";
                    web.Disposed += new EventHandler(web_Disposed);
                    web.Dispose();
                    return;
                }
                eventBuffer="UpdateContractList";
                web.Disposed += new EventHandler(web_Disposed);
                web.Dispose();
            }
        }

private void web_Disposed(object sender, EventArgs e)
        {
            FireEvent(eventBuffer);
            GC.Collect();
            thread.Abort();
        }

Вставьте следующий код после загрузки страницы

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);
}
Другие вопросы по тегам