web-dev-qa-db-fra.com

Comment réparer la fuite de mémoire dans IE Contrôle WebBrowser?

J'essaie d'intégrer un contrôle WebBrowser dans une application Winform C #. Cela semble assez facile. Cependant, j'ai découvert que le contrôle WebBrowser consomme beaucoup de mémoire chaque fois que j'appelle la méthode Navigate. La mémoire n'est jamais libérée. L'utilisation de la mémoire augmente et grandit…

Beaucoup de gens sur le net ont exactement le même problème mais je n’ai pas trouvé de réponse satisfaisante pour le moment. Voici les meilleures discussions sur ce problème que j'ai trouvées jusqu'à présent:

Fuite de mémoire dans IE contrôle WebBrowser

Une personne a suggéré une mise à niveau vers IE8 pour résoudre le problème.

Cependant, j'ai besoin d'une solution qui fonctionne, que la dernière version IE de l'utilisateur soit installée ou non. Je n'ai pas de contrôle sur l'environnement des utilisateurs.

Quelqu'un sait-il comment libérer la mémoire prise par le contrôle WebBrowser? Existe-t-il des solutions de contournement? Existe-t-il des alternatives au contrôle WebBrowser?

Mise à jour: Je viens de faire quelques tests supplémentaires. Au travail, j'utilise Windows XP et IE6. La mémoire ne grandit pas là. La mémoire augmente lors de l'appel de la méthode de navigation mais est libérée après un certain temps. À la maison, je cours sous Vista et je suis passé à IE8. Ici aussi je ne vois plus le problème. Il semble que le problème soit spécifique à IE7. La question devrait donc être reformulée comme suit: "Comment réparer la fuite de mémoire dans IE WebBrowser Control lorsque IE7 est installé". Quelqu'un peut-il confirmer que ce problème est spécifique à IE7?

29
Rainer Falle

Je viens de créer une application simple avec un contrôle de navigateur Web pour essayer de dupliquer vos résultats. Ce que j’ai trouvé, c’est que oui, chaque fois que vous accédez à une page, la mémoire utilisée augmente considérablement. CEPENDANT, CE N'EST PAS une fuite de mémoire, car si vous continuez à naviguer, vous verrez qu'après quelques instants, la mémoire diminue considérablement, ce qui indique que le garbage collector a agi comme il se doit. Pour le prouver, j’ai forcé le ramasse-miettes à le ramasser après chaque appel de Navigate, et la mémoire totale utilisée est restée pratiquement égale à la même quantité après chaque appel de navigation. 

Ainsi, même s’il stocke de la mémoire à chaque fois que vous "naviguez", ce n’est PAS une fuite de mémoire et la mémoire sera libérée. Si cela se produit trop rapidement, appelez simplement GC.Collect ();

8
BFree

Le BASIC IDEA est,

"Tue-moi et renais." 

Windows résoudra tous les problèmes de mémoire.

mais si vous fermez d'abord votre application, vous ne pourrez pas en créer une nouvelle.

Donc, COMMENCEZ UN NOUVEAU ET FERMEZ LE PLUS VIEUX.

Commencez par en activer un nouveau et éteignez un ancien.


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

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

S'il y a un paramètre,

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

Allez à 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());
    }

et, allez à Form1.cs et créez un autre Form1 ()

    public Form1()
    {
        InitializeComponent();
    }

    public Form1(string dance_name)
    {
        InitializeComponent();

        ...
    }

ou vous pouvez utiliser le fichier temporaire !!!

6
Yong-nam Kim

Selon MSDN , le contrôle System.Windows.Forms.WebBrowser est un wrapper géré pour le contrôle ActiveX WebBrowser et utilise la version de celui-ci installée sur l'ordinateur de l'utilisateur.

Vous pouvez trouver la méthode Dispose (bool) dans les métadonnées de la classe WebBrowser (Appuyez sur F12 dans Visual Stuio) pour libérer la ressource non gérée. (NOT Dispose ())

Le code ici

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

Mais si vous essayez d'appeler WebBrowser.Dispose (bool), l'erreur du compilateur CS1540 est affichée.

La classe WebBrowser supporte la méthode Dispose (bool), MAIS nous ne pouvons pas l'utiliser.
Je pense que la classe WebBrowser a été conçue par le mauvais sens.

J'ai une idée d'appeler WebBrowser.Dispose (true).
IT IS TRÈS SIMPLE! mais ce n'est pas un bon moyen.

Exemple de code ici (3 boutons et 1 zone de texte requise)

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

Ce code peut empêcher une fuite de mémoire.

En résumé, Vous avez besoin d'un seul cours.

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

Il existe un contrôle alternatif qui utilise Gecko (le moteur utilisé par Firefox) au lieu de Trident et fonctionne très bien avec les interfaces MSHTML.

Vos pages seront rendues dans Gecko et vous aurez un contrôle total sur les paramètres, les plugins, la sécurité et toutes les autres fonctionnalités personnalisables d'un navigateur.

L'inconvénient est que vous devrez envoyer Gecko avec votre application. J'ai utilisé l'équivalent de Firefox 2 pour la dernière fois et la taille était d'environ 8 Mo. 

Il y a quelque temps, j'ai publié une application qui comparait IE et le rendu de Firefox, les deux se mettant à jour au fur et à mesure de l'édition du CSS. Je n'ai pas rencontré les problèmes de mémoire que vous avez rencontrés avec le contrôle du navigateur Web, mais j'ai trouvé le contrôle Gecko très facile à utiliser. Il n'a pas la même classe d'encapsuleur géré que le contrôle .net WebBrowser, mais il est assez facile de contourner ce problème.

3
Matthew Brindley

Il semble que la méthode Navigate () garde toutes les pages visitées en mémoire car vous pouvez utiliser la méthode GoBack (), il n'y a aucune "fuite de mémoire" en fait. Mon programme visite la même URL à plusieurs reprises. Le problème de "fuite de mémoire" peut être éliminé à l'aide de la méthode Refresh () de la méthode Navigate (), suivie d'un GC.Collect (). Le code est le suivant:

       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
ghostyguo

J'ai rencontré ce problème en écrivant une petite application de "diaporama" pour différentes pages intranet utilisées par mon entreprise. La solution la plus simple que j'ai trouvée consistait à redémarrer l'application après une période de temps fixe, une heure dans mon cas. Cette solution a bien fonctionné pour nous car il n'y avait pas beaucoup d'interaction de l'utilisateur avec le navigateur.

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

Cela suppose bien entendu que vous disposiez d'un mécanisme de persistance des données.

1
D-Jones

Il y a une fuite de mémoire connue dans le contrôle WebBrowser. Voir l'article suivant de la base de connaissances Microsoft - KB893629 .

1
Dave Black

Je pense que cette question est restée sans réponse depuis longtemps maintenant. Autant de discussions avec la même question mais pas de réponse concluante.

J'ai trouvé un moyen de contourner ce problème et je voulais partager avec vous tous ceux qui sont toujours confrontés à ce problème.

step1: Créez un nouveau formulaire, par exemple form2, et ajoutez-y un contrôle de navigateur Web. step2: dans le formulaire1 où vous avez le contrôle de votre navigateur Web, supprimez-le simplement. step3: Maintenant, allez à Form2 et rendez le modificateur d'accès de ce contrôle Webbrowser public afin qu'il soit accessible dans Form1 step4: Créez un panneau dans form1, créez un objet de form2 et ajoutez-le au panneau. Form2 frm = new Form2 (); frm.TopLevel = false; frm.Show (); panel1.Controls.Add (frm); step5: Appelez le code ci-dessous à intervalles réguliers frm.Controls.Remove (frm.webBrowser1); frm.Dispose ();

C'est tout. Désormais, lorsque vous l'exécutez, vous pouvez voir que le contrôle Webbrowser est chargé. Il sera éliminé à intervalles réguliers et l'application ne sera plus suspendue.

Vous pouvez ajouter le code ci-dessous pour le rendre plus efficace.

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


    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
0
nh53019

J'ai rencontré ce problème avec un simple contrôle WebBrowser sur un formulaire. Il a navigué sur une page Web et y est resté. Selon le gestionnaire de tâches dans les 10 minutes, il a consommé 2 Go de mémoire de 120 Mo.

Une solution simple pour mon projet consistait à accéder aux propriétés de contrôle du navigateur Web dans Visual Studio et à définir le paramètre "AllowNavigation" sur false. Maintenant, quand je lance mon programme, il reste à 120mb. J'espère que ça aidera quelqu'un!

0
tonyk123

J'utilise le contrôle Web dans une application mais, depuis que mon application navigue sur une seule page, je n'ai pas remarqué le problème que vous avez mentionné ... Il y a un autre contrôle Web qui est en fait un wrapper et je ne sais pas s'il en a un. le même problème ou pas .. Vous pouvez le trouver ici .

0
Beatles1692

Cela a fonctionné pour moi, je ne suis pas sûr que 100% de la mémoire soit effacée, il semble que les pages en cache sont conservées, mais que l'utilisation de la mémoire vive ne dépasse plus 500 Mo, elle reste à 60 Mo.

mon programme va à plusieurs reprises sur le même site, 3 pages différentes, une seule fois par utilisation, sans explorer un grand nombre de pages ou quoi que ce soit.

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();
        }
0
Captain Awesome

Je rencontrais le même problème, au lieu de naviguer vers une nouvelle page, mais simplement de réécrire la même page html à l'aide de l'objet system.oi.streamreader/writer et d'appeler une actualisation. Évidemment, cela ne fonctionnera pas dans une situation où le contenu du navigateur est alimenté en ligne, mais cela me convient.

De plus, j'utilise actuellement plus de 8 contrôles de navigateur actifs en même temps pour générer des rapports via javascript dans mon application .net. Lorsqu'un utilisateur active un navigateur, le code HTML vers lequel pointent les autres navigateurs est effacé et les navigateurs sont actualisés. Avec 8 navigateurs fonctionnant avec ces deux méthodes, je peux facilement conserver mon application sous l'utilisation de la mémoire de Firefox avec seulement 3 onglets ouverts.

0
JNevill

Nous sommes presque à la fin de 2017 et ce bug ennuyeux est toujours présent dans WebBrowser.

J'ai essayé toutes les solutions ici et aucune d'entre elles n'a fonctionné pour moi. La fuite de mémoire persiste toujours ... Le plus étrange, c'est que lorsque j'appelle:

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

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

Cela réduit effectivement beaucoup la mémoire! mais lorsque la prochaine instruction Navigate est appelée, toute la mémoire perdue revient dans la portée .... comme (si la mémoire est à 450 Mo. Cette instruction est réduite à environ 20 Mo et juste après avoir appelé .Navigate (chaîne de caractères), elle passe à 460 Mo. et la fuite de mémoire continue ...

J'ai même essayé Dispose () en naviguant dans about: vide avant la page suivante, en définissant l'objet navigateur Web sur la valeur null et en en créant un nouveau. Toutes ces tentatives tombent dans une fuite de mémoire ... c'est vraiment frustrant ... Y a-t-il d'autres solutions?

0
rickrvo

Collez le code suivant après le chargement de la page

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);
}
0
Stefan Kruger

Il suffit de déclarer le contrôle WebBrowser en utilisant le mot clé "using". Il cessera de perdre de la mémoire lors de l'appel de la méthode Navigate (). Je viens de le tester et cela a bien fonctionné pour moi.

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

J'ai eu les mêmes problèmes similaires. J'envoyais plus de 5000 demandes de navigation via un navigateur Web pour effacer des pages dynamiques. Après environ 50 requêtes, je manquais de mémoire car l'utilisation de la mémoire des requêtes de navigation n'était pas libérée après chaque requête. J'ai utilisé webBrowser.Dispose () après la navigation et le problème a été résolu. Cela n'a pas à voir avec IE7 ou autre. J'utilise IE 11 et j'ai le même problème. C’est parce que je ne disposais pas des objets de navigation ... Je pense que cela aide.

0
Try_Learning

J'ai cherché partout sur Internet et j'étais incapable de trouver une réponse à ce problème. Je l'ai corrigé en utilisant ce qui suit:

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

Je sais que ce n'est pas la solution la plus jolie ni la solution parfaite, mais cela a très bien fonctionné pour moi.

0
Layla