2 votes

Exception d'absence de mémoire - mémoire non gérée

Je travaille sur un scraper web, et il fonctionne bien en général. Il peut parcourir des milliers de pages sur la plupart des sites et terminer sans problème.

Sur quelques sites, je constate le même problème à plusieurs reprises.

Insufficient memory to continue the execution of the program.

Edita: J'ai utilisé perfmon pour déterminer que la fuite se produit dans la mémoire non gérée. Je le sais parce que les "octets privés" continuent d'augmenter au fur et à mesure que le programme s'exécute, alors que les octets dans tous les tas restent stables.

(en fait, il monte et descend, mais progressivement. il manque généralement de mémoire dans la section de code que j'ai listée ci-dessus, mais je ne pense pas que cette section soit la cause, mais plutôt une première victime probable parce qu'elle utilise beaucoup de mémoire... je pense qu'elle la libère ensuite cependant)


Edit 2 :

J'ai suivi les instructions de ce site : http://www.codeproject.com/Articles/42721/Best-Practices-No-5-Detecting-NET-application-memo

et j'ai utilisé debugDiag pour inspecter le programme.

Après avoir analysé les données, le programme de débogage m'a dit ce qui était responsable de la fuite :

jscript.dll is responsible for 1.10 GBytes worth of outstanding allocations. The following are the top 2 memory consuming functions:

jscript!Parser::GenerateCode+167: 498.19 MBytes worth of outstanding allocations.

jscript!NoRelAlloc::PvAlloc+96: 292.99 MBytes worth of outstanding allocations.

Je ne fais pas référence à jscript.dll dans mon application, elle doit être utilisée par les contrôles du navigateur Web que j'utilise.

System.Windows.Forms.WebBrowser

C'est ce que je pense, en tout cas.

Je reçois également un message qui s'affiche avec le titre "Message From webpage" et qui dit quelque chose comme "out of memory at line X".

J'ai donc pensé que je pouvais simplement me débarrasser des objets du navigateur web et récupérer ma mémoire - j'ai donc ajouté un bouton avec le code suivant :

Me.wbMain.Dispose() 'dispose all of thwe web-browsers
frmDebugger.wbDebugMain.Dispose()
Me.WBNewWin.Dispose()

GC.Collect() 'just for the heck of it

Donc, après l'avoir utilisé pendant un certain temps, j'ai arrêté de gratter et j'ai cliqué sur mon nouveau bouton... cela n'a fait aucune différence. Je regardais le total des "Private Bytes" dans perfmon, et ça n'a même pas bougé.

Des idées, quelqu'un ?


Edit 3 :

J'ai essayé plusieurs des solutions recommandées, mais aucune ne semble fonctionner.

Quelqu'un a suggéré que cela pouvait être dû au fait que les images ne sont pas effacées du cache, mais j'ai désactivé le chargement des images, donc je sais que ce n'est pas le problème.

J'ai également entendu dire qu'IE7 avait un problème et que la mise à niveau vers IE8 le résoudrait. J'ai IE8 et il perd toujours de la mémoire.

Quelqu'un a suggéré que minimiser le formulaire avec le contrôle du navigateur web libérerait de la mémoire. J'ai essayé, et cela ne fait pas de différence.

On m'a également dit que je ne devais pas m'attendre à ce que l'utilisation de la mémoire diminue simplement, car je dois attendre le ramasseur d'ordures. Ce n'est pas une fuite dans le code géré, donc GC.Collect() ne fera rien. Elle se trouve dans la mémoire non gérée. Apparemment, la fonctionnalité javascript utilise une mémoire différente, et il n'y a pas de moyen manuel de forcer une collecte. Mais il en arrive au point où il se plante, donc il y a manifestement un problème.

J'ajoute une prime de 50 à cette question, et je l'attribuerai à toute personne qui m'aidera à résoudre la fuite. Je voulais essayer cette solution : http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse mais je ne parviens pas à trouver l'équivalent en vb.net. J'ai essayé des convertisseurs en ligne, et ils se trompent lors de la conversion de ce code (bien qu'ils fonctionnent bien pour d'autres codes que j'ai convertis dans le passé).

Si je ne parviens pas à résoudre la fuite, je l'attribuerai à quiconque convertira la page que j'ai mentionnée ci-dessus de c# en vb.net.

Mon plan de secours est de créer une application séparée qui ne contient que le navigateur web, et de communiquer avec ce processus, jusqu'à ce qu'il soit à court de mémoire, auquel cas je le redémarre (la mémoire est libérée lorsque je ferme complètement mon application). Cette méthode est loin d'être idéale pour mon application, car le navigateur web est étroitement lié à mon projet.


Edit 4

J'ai essayé d'implémenter l'injection javascript suggérée - voici mon code :

(Je le déclenche juste avant de naviguer vers une nouvelle page)

Public Shared Sub Clean_JS(ByRef wb As System.Windows.Forms.WebBrowser)

        Dim args As Object() = {"document.body"}

        Dim head As HtmlElement = wb.Document.GetElementsByTagName("head")(0)

        Dim scriptEl0 As HtmlElement = wb.Document.CreateElement("script")
        Dim element0 As mshtml.IHTMLScriptElement = DirectCast(scriptEl0.DomElement, mshtml.IHTMLScriptElement)
        element0.text = "function ReleaseHandler() {" + vbCrLf + "        var EvtMgr = (function() {" + vbCrLf + "            var listenerMap = {};" + vbCrLf + " " + vbCrLf + "            // Public interface" + vbCrLf + "            return {" + vbCrLf + "                addListener: function(evtName, node, handler) {" + vbCrLf + "                    node[""on"" + evtName] = handler;" + vbCrLf + "                    var eventList = listenerMap[evtName];" + vbCrLf + "                    if (!eventList) {" + vbCrLf + "                        eventList = listenerMap[evtName] = [];" + vbCrLf + "                    }" + vbCrLf + "                    eventList.push(node);" + vbCrLf + "                }," + vbCrLf + " " + vbCrLf + "                removeAllListeners: function() {" + vbCrLf + "                    for (var evtName in listenerMap) {" + vbCrLf + "                        var nodeList = listenerMap[evtName];" + vbCrLf + "                        for (var i = 0, node; node = nodeList[i]; i++) {" + vbCrLf + "                            node[""on"" + evtName] = null;" + vbCrLf + "                        }" + vbCrLf + "                    }" + vbCrLf + "                }" + vbCrLf + "            }" + vbCrLf + "        })();" + vbCrLf + "    }"
        head.AppendChild(scriptEl0)

        Dim scriptEl1 As HtmlElement = wb.Document.CreateElement("script")
        Dim element1 As mshtml.IHTMLScriptElement = DirectCast(scriptEl1.DomElement, mshtml.IHTMLScriptElement)
        element1.text = "function ReleaseHandler() {" + vbCrLf + "        var EvtMgr = (function() {" + vbCrLf + "            var listenerMap = {};" + vbCrLf + " " + vbCrLf + "            // Public interface" + vbCrLf + "            return {" + vbCrLf + "                addListener: function(evtName, node, handler) {" + vbCrLf + "                    node[""on"" + evtName] = handler;" + vbCrLf + "                    var eventList = listenerMap[evtName];" + vbCrLf + "                    if (!eventList) {" + vbCrLf + "                        eventList = listenerMap[evtName] = [];" + vbCrLf + "                    }" + vbCrLf + "                    eventList.push(node);" + vbCrLf + "                }," + vbCrLf + " " + vbCrLf + "                removeAllListeners: function() {" + vbCrLf + "                    for (var evtName in listenerMap) {" + vbCrLf + "                        var nodeList = listenerMap[evtName];" + vbCrLf + "                        for (var i = 0, node; node = nodeList[i]; i++) {" + vbCrLf + "                            node[""on"" + evtName] = null;" + vbCrLf + "                        }" + vbCrLf + "                    }" + vbCrLf + "                }" + vbCrLf + "            }" + vbCrLf + "        })();" + vbCrLf + "    }"
        head.AppendChild(scriptEl1)

        wb.Document.InvokeScript("ReleaseHandler")
        wb.Document.InvokeScript("purge", args)

End Sub

malheureusement, je vois toujours les octets privés augmenter dans perfmon.

Quelqu'un peut-il voir des failles dans ma logique ? J'essaie de mettre en œuvre cette solution : http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse

btw - je l'ai testé en utilisant un code simple comme celui-ci :

object[] args = {"my important message"};
webBrowser1.Document.InvokeScript("alert",args);

et ceci :

Dim head As HtmlElement = wb.Document.GetElementsByTagName("head")(0)
Dim scriptEl As HtmlElement = wb.Document.CreateElement("script")
Dim element As mshtml.IHTMLScriptElement = DirectCast(scriptEl.DomElement, mshtml.IHTMLScriptElement)
element.text = "function sayHello() { alert('hello') }"
head.AppendChild(scriptEl)
wb.Document.InvokeScript("sayHello")

et il a montré le message dans les deux cas de test.

Curieusement, lorsque j'ai essayé de tester l'injection de script en faisant ceci :

    Dim head As HtmlElement = wbMain.Document.GetElementsByTagName("head")(0)
    Dim scriptEl As HtmlElement = wbMain.Document.CreateElement("script")
    Dim element As mshtml.IHTMLScriptElement = DirectCast(scriptEl.DomElement, mshtml.IHTMLScriptElement)
    element.text = "function sayHello() { alert('hello') }"
    head.AppendChild(scriptEl)
    wbMain.Document.InvokeScript("sayHello")

    RTB_RawHTML.Text = "TEST" + vbCrLf + wbMain.DocumentText

Je n'ai pas vu le code injecté se refléter dans la zone de texte - le seul changement que j'ai vu est l'apparition du mot "test" (j'exécute le code RTB_RawHTML.Text = wbMain.DocumentText lorsque les pages finissent de se charger à partir de l'événement documentCompleted...).

0voto

JDB Points 8608

Le code dans votre article référencé n'est pas C#, c'est Javascript. Je pense que l'idée serait d'injecter le JS dans votre page HTML afin qu'il puisse s'exécuter lorsque la page se décharge, ce qui nettoiera les événements JS existants.

Vous pouvez consulter cet article pour ajouter du JS à une page dans votre contrôle WebBrowser :
http://www.codeproject.com/Articles/94777/Adding-a-Javascript-Block-Into-a-Form-Hosted-by-We

Dim scriptText As String =
    <string>
        function ReleaseHandler() {
                var EvtMgr = (function() {
                    var listenerMap = {};

                    // Public interface
                    return {
                        addListener: function(evtName, node, handler) {
                            node["on" + evtName] = handler;
                            var eventList = listenerMap[evtName];
                            if (!eventList) {
                                eventList = listenerMap[evtName] = [];
                            }
                            eventList.push(node);
                        },

                        removeAllListeners: function() {
                            for (var evtName in listenerMap) {
                                var nodeList = listenerMap[evtName];
                                for (var i = 0, node; node = nodeList[i]; i++) {
                                    node["on" + evtName] = null;
                                }
                            }
                        }
                    }
                })();
            }

        function purge(d){
            var a = d.attributes, i, l, n;
            if (a) {
                for (i = a.length - 1; i >= 0 ; i -= 1) {
                    n = a[i].name;
                    if (typeof d[n] === 'function') {
                        d[n] = null;
                    }
                }
            }
            a = d.childNodes;
            if (a) {
                l = a.length;
                for (i = 0; i < l; i += 1) {
                    purge(d.childNodes[i]);
                }
            }
        }

    <string>

Dim head As HtmlElement = webBrowser1.Document.GetElementsByTagName("head")(0)
Dim script As HtmlElement = webBrowser1.Document.CreateElement("script")
Dim domElement As IHTMLScriptElement = CType(script.DomElement, IHTMLScriptElement)
domElement.text = scriptText
head.AppendChild(script)

Je n'ai pas testé ce code (je ne suis pas vraiment sûr de la façon dont je m'y prendrais puisque vous n'avez pas proposé d'exemple de code vous-même)... il s'agit plutôt d'une suggestion sur la façon dont vous pourriez procéder. Je n'ai jamais essayé d'insérer du JS dans un contrôle WebBrowser, donc je ne sais pas vraiment comment vous pourriez l'exécuter (puisque, en théorie, le JS aura déjà été exécuté après le chargement de la page, donc votre JS injecté serait "en retard sur la fête").

Vous devrez également trouver un moyen de câbler le document de manière à ce qu'il appelle ces deux fonctions lorsqu'il se décharge. L'idée est d'éliminer les fuites de mémoire JS en éliminant les objets et les événements JS, donc le simple fait de déclarer les fonctions est insuffisant. J'ai vu beaucoup d'articles en ligne expliquant comment l'événement OnBeforeUnload est cassé dans le contrôle WebBrowser (il ne se déclenche pas correctement), donc vous risquez d'avoir du pain sur la planche.

0voto

Kasnady Points 1685

Vous pouvez peut-être essayer un code pour ne pas enregistrer le cookie sur l'ordinateur de l'utilisateur. Parce qu'un élément temporaire peut causer plusieurs problèmes à l'ordinateur de l'utilisateur.

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