30
votes

I am trying to embed a WebBrowser Control in a C# Winform Application. This sounds easy enough. However I discovered that the WebBrowser control eats up a lot of memory every time I call the Navigate method. The memory is never released. The memory usage grows and grows…

Many people on the net having the exact same problem but I haven’t found a satisfying answer yet. This is the best discussions about this issue I found so far:

Memory Leak in IE WebBrowser Control

One person suggested an upgrade to IE8 to fix the problem.

However I need a solution that works whether the user has the latest IE version installed or not. I do not have control over the users environment.

Does anybody know how to release the memory taken by the WebBrowser control? Are there workarounds? Are there alternatives to the WebBrowser control?

Update: I just did a few more tests. At work I am running Windows XP and IE6. The memory is not growing there. The memory increases when calling the navigate method but is being released after a while. At home I am running Vista and upgraded to IE8. Here I also do not see the problem anymore. It looks like the issue is specific to IE7. So the question should be rephrased to "How to Fix the Memory Leak in IE WebBrowser Control when IE7 is installed". Can anybody confirm that this problem is specific to IE7?

18
There is a memory leak if you add event handlers to any of the navigated elements. To fix, you'll have to keep a dictionary of all elements (include the top level document too), then in the OnDocumentCompleted( ) function, remove the event handlers one by one while calling marshall.ReleaseComObject(o.DomDocument) in a loop, then finally release the top level document via Marshal.ReleaseComObject(document.DomDocument).Brain2000

18 Answers

10
votes

my app was also constantly consuming memory when navigating, and not releasing anymore. i fount the solution for me here: http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8

for completeness ill post the notable excerpt:

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

-- code to call when you want to reduce the memory

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

all honors to: 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 for posting the solution.

and http://ict-engineer.blogspot.com/2010/10/net-webbrowser-control-memory-leak.html for SEO'ing it right, so i could find it ;)

greetings

edit: if this helps you to quickly solve an issu - good. but you should overthing your application design, the pattern you use if any , refactore the thing if you build onto that much longer ....

7
votes

I just created a simple app with a web browser control to try and duplicate your results. What I found was that yes, every time you navigate to a page, the memory being used increases significantly. HOWEVER, this is NOT a memory leak, because if you keep navigating, you'll see that after a short while, the memory drops significantly, indicating that the garbage collector did it's thing. To prove it, I forced the Garbage Collector to collect after every time I called Navigate, and the overall memory used stayed put at almost the same amount after every navigate call.

So while it DOES rack up memory every time you "Navigate" it's NOT a memory leak, and you the memory will be released. If it's raking up too quickly, just call GC.Collect();

7
votes

The BASIC IDEA is,

"Kill myself, and reborn."

Windows will solve all memory problems.

but if you Close your application first, you can't start a new one.

So, START A NEW ONE, and CLOSE THE OLDER ONE.

First turn on a new one, and turn off an old one.


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

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

If there is a parameter,

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

Go to 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());
    }

and, Go to Form1.cs and make another Form1()

    public Form1()
    {
        InitializeComponent();
    }

    public Form1(string dance_name)
    {
        InitializeComponent();

        ...
    }

or you can use temp file !!!

3
votes

There's an alternative control that uses Gecko (The engine Firefox uses) instead of Trident and works very well with the MSHTML interfaces.

Your pages will render in Gecko, and you'll have complete control over the settings, plugins, security and any other customisable features of a browser.

The downside is that you'll need to ship Gecko with your app, I last used the equivalent of Firefox 2 and it was around 8MB.

I released an app quite a while ago that compared IE and Firefox rendering alongside each other, both updating as you edited the CSS. I didn't run into the memory problems you've had with the web browser control, but I found the Gecko control very easy to work with. It doesn't have the same managed wrapper class that the .net WebBrowser control has, but it's easy enough to work around that.

3
votes

According to MSDN, The System.Windows.Forms.WebBrowser control is a managed wrapper for the ActiveX WebBrowser control, and uses whichever version of the control is installed on the user's computer.

You can find Dispose(bool) method in metadata of WebBrowser class(Press F12 in Visual Stuio) to release unmanaged resource.(NOT Dispose())

The code here

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

But if you try to call WebBrowser.Dispose(bool), compiler error CS1540 is shown.

WebBrowser class supports Dispose(bool) method, BUT we can't use that.
I think WebBrowser class was designed by wrong way.

I have a idea to call WebBrowser.Dispose(true).
IT IS VERY SIMPLE! but it's not a good way.

Sample Code in here(3 Buttons, and 1 TextBox need)

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

This code can prevent Memory Leak.

In summary, You need just one class.

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

I looked all over the internet and I was unable to find an answer to this problem. I fixed it using the below:

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

I know this is not the prettiest or perfect solution, but it worked very well for me. -- Layla

1
votes

There is a known Memory Leak in the WebBrowser control. See the following Microsoft KB article - KB893629.

1
votes

I think this question has gone unanswered for a long time now. So many threads with the same question but not conclusive answer.

I have found a work around for this issue and wanted to share with you all who are still facing this issue.

step1: Create a new form say form2 and add a web-browser control on it. step2: In the form1 where you have your webbrowser control, just remove it. step3: Now, go to Form2 and make the access modifier for this webbrowser control to be public so that it can be accessed in Form1 step4: Create a panel in form1 and create object of form2 and add this into panel. Form2 frm = new Form2 (); frm.TopLevel = false; frm.Show(); panel1.Controls.Add(frm); step5: Call the below code at regular intervals frm.Controls.Remove(frm.webBrowser1); frm.Dispose();

Thats it. Now when you run it, you can see that webbrowser control loaded and it will get disposed at regular intervals and there is no more hanging of the application.

You can add the below code to make it more efficient.

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


    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
1
votes

I ran into this problem while writing a small "slideshow" application for different intranet pages used by my company. The simplest solution I found was restarting the application after some fixed period of time, an hour in my case. This solution worked well for us because there wasn't a lot of user interaction with the browser.

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

This assumes of course that you have a data persistence mechanism in place.

1
votes

It seems the Navigate() method keeps all the visited pages in memory as you can use the GoBack() method, there is no "memory leak" in fact. My program visits the same Url repeatedly. The "memory leak" problem can be eliminated by using the Refresh() method instand of Navigate() method, followed by a GC.Collect(). The Code is in the following:

       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
1
votes

It's almost end of 2017 and still this annoying bug is present in WebBrowser.

I've tried all solutions here and none of them worked for me. Memory Leak still persists... The shtrangest thing is that when I call:

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

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

It does actually reduce the memory alot! but when the next Navigate instruction is called all the leaked memory comes back in scope.... like (if memory is at 450mb.. this instructions reduces to about 20 mb and right after calling .Navigate(string) again it jumps to 460mb and memory leak goes on...

I even tried Dispose(), navigating to about:blank before the next page, setting webbrowser object to null and creating a new one. All these attempts fall in memory leak... this is really frustrating... Any other solutions?

1
votes

I experienced this problem with a simple webbrowser control on a form. It navigated to one web page and stayed there. According to task manager within 10 minutes it ate up 2gb of memory from 120mb.

A simple solution for my project was to go to the webbrowser controls properties in visual studio and change the 'AllowNavigation' to false. Now when I run my program it stays at 120mb. I hope this helps someone!

0
votes

I'm using the Web Control in an application but since my application navigates to one page only I haven't noticed the issue you mentioned. There's another web control that is actually a wrapper and I don't know if it has the same problem or not. You can find it here.

0
votes

I was running into the same problem, as an alternative, instead of navigating to a new page, I simply rewrote the same html page using the system.oi.streamreader/writer object and calling a refresh. Obviously that won't work in a situation where content for the browser is being fed online, but it's doing the trick for me.

Also, I'm currently using 8+ browser controls all active at the same time to serve reporting through javascript inside my .net app. As a user makes one browser active, the html to which the other browsers are pointing, are cleared and the browsers refreshed. With 8 browsers running with these two methods I can easily keep my app well under the memory usage of Firefox with just 3 tabs open.

0
votes

I had same similar problems. I was sending more than 5000 navigation requests via web browser to scrape dynamic pages. After about 50 request, I would run out of memory as navigation request memory usage was not released after each request. I used webBrowser.Dispose() after the navigation, and it solved the problem. It does not have to do with IE7 or so. I am using IE 11 and got same problem. It was because I was not disposing the navigation Objects. Hope this helps.

0
votes

Just declare the WebBrowser control using the "using" keyword. It will stop leaking memory when calling the Navigate() method anymore. I just tested it and it worked fine for me.

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

This worked for meand my app no longer climbs to 500MB ram usage anymore, it stays at 60MB forever. The trick is to set the webbrowser to null after disposing it, and before calling GC.

my program repeatedly goes to the same site, 3 different pages, only once per use, not crawling large numbers of pages or anything.

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)
        {
            WebBrowser web = (WebBrowser)sender;
            FireEvent(eventBuffer);
            **web = null;**
            GC.Collect();
            thread.Abort();
        }
-1
votes

Paste the following code after page load

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