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

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

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

Утечка памяти в IE WebBrowser Control

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

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

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

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

Ответ 1

мое приложение также постоянно потребляло память при навигации, а не отпускании. Для меня это решение: 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'ing это правильно, поэтому я мог его найти;)

привет

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

Ответ 2

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

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

Ответ 3

БАЗОВАЯ ИДЕЯ,

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

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

        ...
    }

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

Ответ 4

Существует альтернативный контроль который использует Gecko (использует Firefox) вместо Trident и отлично работает с интерфейсами MSHTML.

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

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

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

Ответ 5

Согласно 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 потребность в текстовом поле)

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

Кажется, что метод Navigate() сохраняет все посещаемые страницы в памяти, так как вы можете использовать метод GoBack(), на самом деле нет утечки памяти. Моя программа неоднократно посещает тот же Url. Проблема "утечка памяти" может быть устранена с помощью метода instresh метода 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

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

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

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

Ответ 13

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

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

step1: Создайте новую форму, напишите 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();

Ответ 14

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

Ответ 15

Это сработало для меня, не уверенный, что 100% очищает память, похоже, все еще сохраняет кешированные страницы, но больше не поднимается до 500 МБ использования RAM, он остается на 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();
        }

Ответ 16

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

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

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

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

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

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

Ответ 17

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

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

Ответ 18

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

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

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

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

Ответ 19

Я испытал эту проблему с помощью простого элемента управления веб-браузером в форме. Он перешел на одну веб-страницу и остался там. Согласно менеджеру задач в течение 10 минут он съел 2 гб памяти от 120 МБ.

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