57 votes

Comment contourner la fuite de mémoire dans le contrôle .NET Webbrowser?

C'est un fait largement connu, vieux problème avec la .NET contrôle Webbrowser.

Résumé: après Avoir de la .NET contrôle webbrowser Accédez à une page augmente l'utilisation de la mémoire qui n'est jamais libérée.

Reproduire la fuite de mémoire: Ajouter un contrôle WebBrowser à un formulaire. L'utiliser pour Naviguer jusqu'à ce que les pages que vous le souhaitez. about:blank œuvres, le défilement vers le bas sur Google Images, jusqu'à ce que votre utilisation est de 100 mo+ et puis en cherchant d'ailleurs à remarquer peine de tout de qui est libéré de la mémoire est une démonstration plus spectaculaire.

Mes exigences actuelles pour une application courante pendant de longues périodes de temps, l'affichage d'un limitées IE7 fenêtre du navigateur. L'exécution de IE7 lui-même avec quelques bâtard d'installation de crochets, les Bho et les stratégies de groupe n'est pas désiré soit, même si c'est à la recherche comme le secours à ce moment. Intégration d'un navigateur web dans une application Windows Forms. À l'aide d'un autre navigateur de base n'est pas une option disponible pour moi. IE7 est nécessaire.

Précédente fils et articles relatifs à cette fuite de mémoire connus:

Souvent proposé des correctifs qui NE fonctionnent PAS:

  • Va différentes pages n'a pas d'importance. about:blank déclenche la fuite. Il ne nécessite pas une page javascript, ou n'importe quelle autre technologie.
  • À l'aide de différentes versions d'Internet Explorer n'a pas d'importance. 7, 8 et 9 présentent tous les mêmes symptômes, et autant que j'en ai entendu, toutes les versions ont la même fuite de mémoire dans le contrôle.
  • Dispose()ing du contrôle n'aide pas.
  • La Collecte des ordures n'aide pas. (En fait, les recherches que j'ai fait en cela indique que la fuite est dans la COM non managé code de l'Webbrowswer de contrôle de l'enveloppe.)
  • La réduction et l'paramètre de processus de mémoire disponible pour l'-1, -1(SetProcessWorkingSetSize() ou simimlar.) seulement réduit l'utilisation de la mémoire physique, n'a pas d'impact sur la mémoire virtuelle.
  • Appel WebBrowser.Stop() n'est pas une solution, et les sauts de fonctionnalités pour l'utilisation de quelque chose, mais la page statique, sans en faire plus que simplement de minimiser la fuite légèrement.
  • Forcer une attente pour un document pour charger complètement avant de Naviguer à l'autre aussi ne l'aide pas.
  • De charger le contrôle dans un autre domaine d'application ne permet pas de résoudre le problème. (Je n'ai pas fait moi-même, mais la recherche montre que d'autres aient pas de succès avec cette route.)
  • L'aide d'un autre wrapper comme csexwb2 n'aide pas, car cela aussi souffre du même problème.
  • Effacement du cache des Fichiers Internet Temporaires ne fait rien. Le problème est dans la mémoire active, pas sur le disque.

La mémoire est effacé lorsque l'ensemble de l'application est fermé et redémarré.

Je suis prêt à écrire mon propre contrôle de navigateur dans la COM ou de l'API Windows directement, si c'est pour assurer solution au problème. Bien sûr, je préférerais un moins compliqué fixer; je préfère éviter d'aller jusqu'aux niveaux inférieurs de faire les choses, parce que je ne veux pas être de réinventer la roue en termes de prise en charge des fonctionnalités d'un navigateur. Letalone la duplication d'IE7 caractéristiques et les comportements non standard en rouleau-votre-propre navigateur de style.

De l'aide?

12voto

Ivan Points 854

Cette fuite semble être une fuite dans une mémoire non managée, de sorte que rien de ce que vous faites dans votre processus va récupérer de la mémoire. À partir de votre post, je vois que vous avez essayé d'éviter la fuite de manière assez approfondie et sans succès.

Je suggère une approche différente, si possible. Créer une application qui utilise le contrôle de navigateur web et commencer à partir de votre application. Utiliser la méthode décrite ici pour intégrer des applications nouvellement créé au sein de votre propre application existante. Communiquer avec cette application à l'aide de la WCF ou .NET remoting. Redémarrez le processus de l'enfant, de temps à autre pour l'empêcher de prendre trop de mémoire.

Bien sûr, cela est assez compliqué solution et le processus de redémarrage pourrait probablement moche. Vous pourriez peut-être recours à la relance de l'ensemble de l'application de navigateur à chaque fois que l'utilisateur accède à une autre page.

7voto

Sergey Kostrukov Points 363

J'ai pris le udionele code (il a travaillé pour moi, merci!) et changé deux petites choses:

  1. IKeyboardInputSite est une interface publique et a la méthode de Désinscription(), de sorte que nous n'avons pas besoin d'utiliser la réflexion après que nous avons reçu une référence à *_keyboardInputSinkChildren* collecte.

  2. Que la vue n'a pas toujours une référence directe à la fenêtre de la classe (en particulier dans MVVM), j'ai ajouté une méthode GetWindowElement(DependencyObject élément) qui retourne la référence en parcourant la via visual arbre.

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

Merci à vous tous!

4voto

udione Points 11

Il existe un moyen de supprimer les fuites de mémoire en utilisant la réflexion et en supprimant les références des champs privés sur mainForm. Ce n'est pas une bonne solution, mais pour les personnes désespérées, voici le 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);
        }
    }
} 
 

3voto

SliverNinja Points 15924

Après avoir lutté contre cet exact de la Mémoire de différentes directions (Win32 WorkingSet / COM SHDocVw interfaces, etc.) avec le WPF WebBrowser composant, j'ai découvert le problème pour nous est jqGrid plugin maintenant sur les ressources non managées dans la IE ActiveXHost et non pas de les relâcher après l'appel de WebBrowser.Dispose(). Cette question est souvent créé par un script Javascript qui ne se comporte pas correctement. La chose étrange est que le Javascript fonctionne très bien régulier, SOIT tout simplement pas de l'intérieur de l' WebBrowser contrôle. J'ai supposer que la collecte des ordures est différente entre les deux points d'intégration que IE ne peut jamais vraiment être fermé.

Une chose que je vous conseille si vous êtes à la création de la source de pages est de supprimer tous les JS composants et en ajoutant lentement. Une fois que vous identifier le fautif JS du plugin (comme nous l'avons fait), il devrait être facile de remédier à ce problème. Dans notre cas, nous avons juste utilisé $("#jqgrid").jqGrid('GridDestroy') supprimer correctement les événements et les éléments DOM associés qu'il a créé. Cela a pris soin de la question pour nous, en invoquant ce lorsque le navigateur est fermé par WebBrowser.InvokeScript.

Si vous n'avez pas la possibilité de modifier le code source des pages, vous accédez - vous auriez à injecter JS dans la page pour nettoyer les DOM les événements et les éléments qui présentent des fuites de mémoire. Ce serait bien si Microsoft a trouvé une solution à ce, mais pour l'instant nous sommes gauche de sondage pour la JS plugins qui doivent nettoyé.

1voto

Layla Points 11

La solution ci-dessous a fonctionné pour moi:

 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
 

Bonne chance, Layla

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X