Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get around the memory leak in the .NET Webbrowser control?

People also ask

How do you prevent memory leaks in .NET applications?

Start the debug diagnostic tool and select 'Memory and handle leak' and click next. Select the process in which you want to detect memory leak. Finally select 'Activate the rule now'. Now let the application run and 'Debugdiag' tool will run at the backend monitoring memory issues.

Can a memory leak happen in managed languages like C #?

A simple answer is that classic memory leaks are impossible in GC environments, as classically a memory leak is leaked because, as an unreferenced block theres no way for the software to find it to clean it up. On the other hand, a memory leak is any situation where the memory usage of a program has unbounded growth.

How do you detect a memory leak in Web application?

Start with metrics such as page load times, HTTP request times, and Core Web Vitals – time to the first byte, first contentful paint. If you use Sematext Experience you'll see a number of other useful metrics for your web applications and websites there. However, metrics themselves are only a part of the whole picture.


This leak appears to be a leak in unmanaged memory, so nothing you do in your process is going to reclaim that memory. From your post I can see that you've tried avoid the leak quite extensively and without success.

I would suggest a different approach if feasible. Create a separate application that uses web browser control and start it from your application. Use the method described here to embed newly created application within your own existing application. Communicate with that application using WCF or .NET remoting. Restart the child process from time to time to prevent it from taking to much memory.

This of course is quite complicated solution and the restart process could probably look ugly. You might maybe resort to restarting the whole browser application every time user navigates to another page.


I took the udione's code (it worked for me, thanks!) and changed two small things:

  1. IKeyboardInputSite is a public interface and has method Unregister(), so we don't need to use reflection after we received a reference to *_keyboardInputSinkChildren* collection.

  2. As view not always has a direct reference to its window class (especially in MVVM) I added a method GetWindowElement(DependencyObject element) which returns the required reference by traversing through visual tree.

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

Thank you all!


There is a way to do clear memory leaks by using reflection and removing references from private fields on mainForm. This is not a good solution but for desperate people here is the code:

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

Having battled this exact Out of Memory issue from different directions (Win32 WorkingSet / COM SHDocVw interfaces, etc.) with the WPF WebBrowser component, I discovered the problem for us was jqGrid plugin holding onto unmanaged resources in the IE ActiveXHost and not releasing them after calling WebBrowser.Dispose(). This issue is often created by Javascript that is not behaving properly. The odd thing is that the Javascript works fine in regular IE - just not from within the WebBrowser control. I surmise that the garbage collection is different between the two integration points as IE can never really be closed.

One thing I'd suggest if you are authoring the source pages is to remove all JS components and slowly adding them back in. Once you identify the offending JS plugin (like we did) - it should be easy to remedy the problem. In our case we just used $("#jqgrid").jqGrid('GridDestroy') to properly remove the events and associated DOM elements it created. This took care of the issue for us by invoking this when the browser is closed via WebBrowser.InvokeScript.

If you don't have the ability to modify the source pages you navigate to - you would have to inject some JS into the page to clean up the DOM events and elements that are leaking memory. It would be nice if Microsoft found a resolution to this, but for now we are left probing for JS plugins that need cleansed.


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

The below solution worked for me:

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

Best of luck, Layla