0

The following (in P.S. of this posting) test code results in memory leakage. Could you please advise how to solve this memory leakage issue? FYI: I'm using VS2010 Prof, .NET Framework 4.0, Win7 Ultimate and IE9. The test code can be activated by using C# code line:

(new WebBrowser_STA_Test()).Main();

Thank you.

P.S. WebBrowser control Automation C# console application test code:

using System;
using System.Windows.Forms;
using System.Threading;
using NUnit.Framework;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace R_and_D_TestConsole
{
    public class WebBrowserThread : IDisposable 
    {
        private Action<string> _logger;
        public WebBrowserThread(Action<string> logger)
        {
            _logger = logger;
        }

        private void log(string message)
        {
            if (_logger != null) _logger(message);
        }

        private Thread _STA_Thread;

        public bool IsAlive
        {
            get
            {
                return (
                    _STA_Thread != null && 
                    _STA_Thread.IsAlive &&
                    !this.ThreadStoppingInProgress &&
                    !this.ThreadStopped
                    ); 
            }
        }

        public void Start()
        {
            log("Starting WebBrowser Thread...");

            _STA_Thread = new Thread(startBrowser);
            _STA_Thread.Name = "WebBrowser STA Thread";
            _STA_Thread.SetApartmentState(ApartmentState.STA);
            _STA_Thread.Start();

        }

        public WebBrowserHandler WebBrowserHandler { get; private set; }
        private void startBrowser()
        {
            log("WebBrowser Thread started.");

            WebBrowserHandler = new WebBrowserHandler(_logger);
            WebBrowserHandler.CreateBrowserInstance();

            Application.Run();
        }

        public bool ThreadStoppingInProgress { get; private set; }
        public bool ThreadStopped { get; private set; }
        public void Stop()
        {
            if (ThreadStoppingInProgress) return;

            try
            {
                ThreadStoppingInProgress = true;

                log("Stopping WebBrowser Thread...");

                log("Disposing WebBrowser Handler...");
                WebBrowserHandler.DisposeBrowserInstance();
                log("WebBrowser Handler partially disposed.");

                _STA_Thread.Abort();
                Application.ExitThread();
                log("WebBrowser Thread stopped.");
            }
            finally
            {
                ThreadStopped = true;
                ThreadStoppingInProgress = false;
            }

        }

        public bool Disposed { get; private set; }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.Disposed)
            {
                if (disposing)
                {
                    if (WebBrowserHandler != null)
                    {
                        WebBrowserHandler.Dispose();
                        WebBrowserHandler = null;
                        log("WebBrowser Handler object instance and its child objects disposed completely (?)");
                    }

                    _logger = null;
                    _STA_Thread = null;
                }

                this.Disposed = true;
            }
        }

    }

    public class WebBrowserHandlerBase : IDisposable
    {
        protected ISynchronizeInvoke _invokeHelper;

        protected WebBrowser _webBrowser;

        private Action<string> _logger;
        public WebBrowserHandlerBase(Action<string> logger)
        {
            _logger = logger;
        }

        protected void log(string message)
        {
            if (_logger != null) _logger(message);
        }

        public bool Activated { get; private set; }
        public void CreateBrowserInstance()
        {

            _webBrowser = new WebBrowser();
            _webBrowser.Visible = true;
            _webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
            _webBrowser.ScriptErrorsSuppressed = true;

            _invokeHelper = _webBrowser as ISynchronizeInvoke;

            this.Activated = true;
        }

        public void DisposeBrowserInstance()
        {
            if (!this.Activated) return;

            Action dispose = () =>
                {
                    log("WebBrowserHandler.DisposeBrowserInstance (1)");
                    _webBrowser.DocumentCompleted -= webBrowser_DocumentCompleted;
                    // the following line results in floating runtime errors - commented...
                    //_webBrowser.Dispose();  

                    log("WebBrowserHandler.DisposeBrowserInstance (2)");
                };

            if (_invokeHelper.InvokeRequired)
            {
                IAsyncResult result = _invokeHelper.BeginInvoke(dispose, null);
                _invokeHelper.EndInvoke(result);
            }
            else dispose(); 

            this.Activated = false;
        }

        public bool Disposed { get; private set; }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.Disposed)
            {
                if (disposing)
                {
                    if (_webBrowser != null)
                    {
                        // The following line results in runtime error:
                        //   'COM object that has been separated from its underlying RCW cannot be used.'
                        //_webBrowser.Dispose();
                        log("Releasing WebBrowser ActiveX instance...");
                        if (_webBrowser.ActiveXInstance != null) Marshal.ReleaseComObject(_webBrowser.ActiveXInstance);
                        _webBrowser = null;
                    }

                    _logger = null;
                }

                this.Disposed = true;
            }
        }

        public bool NavigationCompleted { get; protected set; }
        public virtual void Navigate_Async(string url) { }
        public virtual void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {}
    }

    public class WebBrowserHandler : WebBrowserHandlerBase 
    {
        public WebBrowserHandler(Action<string> logger):base(logger) {}

        public override void Navigate_Async(string url)
        {
            if (!this.Activated) throw new ApplicationException(string.Format("{0} is not yet activated.", this.GetType().Name) ) ; 

            Action<string> navigate = (x) =>
                {
                    NavigationCompleted = false;
                    _webBrowser.Navigate(x); 
                };

            if (_webBrowser.InvokeRequired) _webBrowser.Invoke(navigate, new object[] { url } ); 
            else navigate(url);
        }

        public override void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            log(string.Format(" *** webBrowser_DocumentCompleted: {0} {1}", _webBrowser.ReadyState,  e.Url));

            if (NavigationCompleted) return;

            NavigationCompleted = _webBrowser.ReadyState == WebBrowserReadyState.Complete;

            if (NavigationCompleted)
            {
                log(string.Format(" *** {0} '{1}'", _webBrowser.DocumentText.Length, _webBrowser.DocumentTitle));       
            }
        }
    }

    public class WebBrowser_STA_Test
    {

        private void log(string message)
        {
            System.Console.WriteLine("[{0}] - {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, message);   
        }

        public void Main()
        {
            for (int i =1; i <= 10; i++)
            {
                test();
                GC.Collect(); 
            }
        }

        private void test()
        {
            log("Test started.");

            foreach (string url in new string[] 
                   { 
                   "http://www.google.com", 
                   "http://www.bing.com", 
                   "http://www.yandex.ru" 
                   })
            {

                log(string.Format("+++ TEST URL = '{0}' +++", url));

                bool navigateMethodCalled = false;

                using (WebBrowserThread _wbTest = new WebBrowserThread(log))
                {
                    _wbTest.Start();

                    Application.DoEvents();

                    while (_wbTest.IsAlive)
                    {
                        if (_wbTest.IsAlive &&
                            _wbTest.WebBrowserHandler != null &&
                            _wbTest.WebBrowserHandler.Activated &&
                            !navigateMethodCalled) 
                        { navigateMethodCalled = true; _wbTest.WebBrowserHandler.Navigate_Async(url); }

                        if (_wbTest.IsAlive &&
                            _wbTest.WebBrowserHandler != null &&
                            _wbTest.WebBrowserHandler.NavigationCompleted) _wbTest.Stop();

                        Thread.Sleep(500);
                    }
                }

                log(string.Format("--- TEST URL = '{0}' ---\n", url));
            }

            log("Test finished.");

        }

    }
}
ShamilS
  • 1,410
  • 2
  • 20
  • 40

2 Answers2

0

Paste the following in you documentCompleted function.

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);
}
DaveShaw
  • 52,123
  • 16
  • 112
  • 141
0

Beware that using MaxWorkingSet and MinWorkingSet will force the app to use virtual memory, rather than actual RAM. This couls result in disk thrashing and may not solve the issue.

I have been looking for weeks and i have found no definitive solution to this problem.

Even destroying the web browser object will not free the memory. The only sure way that i know of, is to restart the application.

However, i did find this interesting page, while browing the 'net:

http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse

...they claim to have a solution, although i have not tried it, as it is in C# and my application is in vb.net

(unfortunately, none of the web based converters are willing to convert that code into vb.net without crashing, and since i suck at c#, i have not been able to convert it myself)

Allen
  • 927
  • 8
  • 19
  • thank you for your comment. I have looked at CodeProject article you referred and the JavaScript code injection sample you provided - I may try to use it. But so far the subject issue isn't in my current mainstream TODO list so it might take (quite) some time for me to check if proposed solution would work well here. I have also thought to try to use a dedicated Application Domain to run a Web Browser instance and when it's done to "kill" it together with AppDomain... – ShamilS Mar 07 '13 at 14:36