Come aggirare la perdita di memoria nel controllo .NET Webbrowser?

Questo è un problema vecchio e ampiamente noto con il controllo .NET Webbrowser.

Riepilogo: Avere il controllo del browser Web .NET Passare a una pagina aumenta l’utilizzo della memoria che non viene mai liberato.

Riprodurre la perdita di memoria: aggiungere un controllo WebBrowser a un modulo. Usalo per navigare verso le pagine che desideri. a proposito di: lavori vuoti, scorrendo verso il basso su Google Immagini fino a quando il tuo utilizzo è di 100 MB + e quindi sfogliare altrove per notare che quasi tutta la memoria è libera è una dimostrazione più drammatica.

I miei requisiti attuali per un’applicazione includono l’esecuzione per lunghi periodi di tempo, visualizzando una finestra del browser IE7 limitata. Anche l’esecuzione di IE7 con una configurazione bastarda di hook, BHO e criteri di gruppo non è desiderata, anche se in questo momento sembra che sia il fallback. L’incorporamento di un browser in un’applicazione Windows Form è. L’utilizzo di una diversa base di browser non è un’opzione disponibile per me. È richiesto IE7.

Discussioni e articoli precedenti relativi a questa perdita di memoria nota:

  • http://www.vbforums.com/showthread.php?t=644658
  • Come risolvere la perdita di memoria in IE WebBrowser Control?
  • Perdita di memoria quando si utilizza il controllo WebBrowser WPF in più windows
  • http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8/
  • http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/8a2efea4-5e75-4e3d-856f-b09a4e215ede
  • http://dotnetforum.net/topic/17400-appdomain-webbrowser-memory-leak/

Correzioni spesso proposte che NON FUNZIONANO:

  • Passare a pagine diverse non ha importanza. about: blank fa scattare la perdita. Non richiede una pagina per avere javascript o qualsiasi altra tecnologia aggiuntiva.
  • L’uso di versioni diverse di Internet Explorer non ha importanza. 7, 8 e 9 mostrano tutti gli stessi sintomi e, per quanto ho sentito, tutte le versioni hanno la stessa perdita di memoria nel controllo.
  • Smaltire () il controllo non aiuta.
  • Garbage Collecting non aiuta. (In effetti, la ricerca su cui ho fatto riferimento indica che la perdita è nel codice COM non gestito che il controllo Webbrowswer esegue il wrapping.)
  • Minimizzando e impostando la memoria disponibile del processo su -1, -1 (SetProcessWorkingSetSize () o simimlar.) Riduce solo l’utilizzo della memoria fisica, non ha alcun impatto sulla memoria virtuale.
  • Chiamare WebBrowser.Stop () non è una soluzione e interrompe la funzionalità per l’utilizzo di qualsiasi cosa tranne le pagine Web statiche, senza fare altro che ridurre al minimo la perdita.
  • Forzare un’attesa che un documento si carichi completamente prima di spostarsi su un’altra non aiuta.
  • Caricamento del controllo in un’app separataDomain non risolve il problema. (Non l’ho fatto da solo, ma la ricerca mostra che altri non hanno avuto successo con questo percorso.)
  • L’uso di un diverso wrapper come csexwb2 non aiuta, poiché anche questo ha lo stesso problema.
  • Cancellare la cache dei file temporanei Internet non fa nulla. Il problema è nella memoria triggers, non sul disco.

La memoria viene cancellata quando l’intera applicazione viene chiusa e riavviata.

Sono disposto a scrivere direttamente il mio controllo del browser in API COM o Windows, se questa è una correzione sicura del problema. Certo, preferirei una soluzione meno complicata; Preferisco evitare di scendere ai livelli più bassi per fare le cose, perché non voglio reinventare la ruota in termini di funzionalità supportate di un browser. Letalone duplicazione di funzionalità IE7 e comportamenti non standard in un browser in stile roll-your-own.

Aiuto?

Questa perdita sembra essere una perdita nella memoria non gestita, quindi nulla di ciò che si sta facendo nel processo sta per recuperare quella memoria. Dal tuo post vedo che hai cercato di evitare la fuga abbastanza estensivamente e senza successo.

Suggerirei un approccio diverso se ansible. Creare un’applicazione separata che utilizza il controllo del browser Web e avviarla dall’applicazione. Utilizzare il metodo descritto qui per incorporare l’applicazione appena creata all’interno della propria applicazione esistente. Comunicare con l’applicazione tramite WCF o .NET remoting. Riavviare il processo figlio di volta in volta per evitare che assuma molta memoria.

Questa è ovviamente una soluzione piuttosto complicata e il processo di riavvio potrebbe sembrare brutto. Potresti forse ricorrere al riavvio dell’intera applicazione del browser ogni volta che l’utente naviga su un’altra pagina.

Ho preso il codice di udione (ha funzionato per me, grazie!) E ho cambiato due piccole cose:

  1. IKeyboardInputSite è un’interfaccia pubblica e ha un metodo Unregister () , quindi non è necessario utilizzare reflection dopo aver ricevuto un riferimento alla raccolta * _keyboardInputSinkChildren *.

  2. Poiché la vista non ha sempre un riferimento diretto alla sua class di windows (specialmente in MVVM) ho aggiunto un metodo GetWindowElement (elemento DependencyObject) che restituisce il riferimento richiesto attraversando la struttura visuale.

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

Grazie a tutti!

C’è un modo per cancellare le perdite di memoria usando la reflection e rimuovendo i riferimenti dai campi privati ​​su mainForm. Questa non è una buona soluzione, ma per le persone disperate ecco il codice:

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

Avendo combattuto questo esatto problema di memoria esaurita da diverse direzioni ( interfacce Win32 WorkingSet / COM SHDocVw, ecc. ) Con il componente WebBrowser WPF , ho scoperto che il problema per noi era il plugin jqGrid che tratteneva risorse non gestite in IE ActiveXHost e non li rilasciava dopo chiamando WebBrowser.Dispose() . Questo problema è spesso creato da Javascript che non si comporta correttamente. La cosa strana è che Javascript funziona correttamente con IE regolare, non solo dal controllo WebBrowser . Suppongo che la raccolta dei rifiuti sia diversa tra i due punti di integrazione in quanto IE non può mai essere veramente chiuso.

Una cosa che suggerirei se stai creando le pagine di origine è rimuovere tutti i componenti JS e aggiungerli lentamente. Una volta identificato il plugin JS incriminato ( come abbiamo fatto noi ), dovrebbe essere facile rimediare al problema. Nel nostro caso abbiamo appena usato $("#jqgrid").jqGrid('GridDestroy') per rimuovere correttamente gli eventi e gli elementi DOM associati creati. Ci siamo presi cura del problema invocandolo quando il browser è stato chiuso tramite WebBrowser.InvokeScript .

Se non hai la possibilità di modificare le pagine di origine a cui navighi, dovresti inserire del JS nella pagina per ripulire gli eventi e gli elementi DOM che stanno perdendo memoria. Sarebbe bello se Microsoft avesse trovato una soluzione a questo, ma per ora non ci sono prove per i plugin JS che devono essere puliti .

La soluzione seguente ha funzionato per 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 

Buona fortuna, Layla

Penso che questa domanda sia rimasta senza risposta per molto tempo. Così tanti thread con la stessa domanda ma non la risposta definitiva.

Ho trovato un modo per ovviare a questo problema e ho voluto condividere con voi tutti coloro che stanno ancora affrontando questo problema.

step1: crea un nuovo modulo, pronuncia form2 e aggiungi un controllo del browser web su di esso. step2: Nel form1 in cui hai il controllo del tuo browser, basta rimuoverlo. step3: Ora, andare su Form2 e rendere pubblico il modificatore di accesso per questo controllo del browser Web in modo che sia accessibile in Form1 step4: Creare un pannello in form1 e creare l’object di form2 e aggiungerlo nel pannello. Form2 frm = new Form2 (); frm.TopLevel = false; frm.Show (); panel1.Controls.Add (FRM); step5: chiama il seguente codice a intervalli regolari frm.Controls.Remove (frm.webBrowser1); frm.Dispose ();

Questo è tutto. Ora, quando lo esegui, puoi vedere che il controllo del browser è stato caricato e verrà eliminato a intervalli regolari e non sarà più ansible appendere l’applicazione.

È ansible aggiungere il codice seguente per renderlo più efficiente.

  IntPtr pHandle = GetCurrentProcess(); SetProcessWorkingSetSize(pHandle, -1, -1); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); 

Credo che sia più un problema di .Net Framework piuttosto che un controllo del browser web. Collegandosi all’evento navigato del browser con un gestore che disporrà il browser e quindi navigando su about: blank sarà una buona soluzione. Per esempio:

 private void RemoveButton_Click(object sender, RoutedEventArgs e) { var browser = (WebBrowser) _stackPanel.Children[_stackPanel.Children.Count - 1]; _stackPanel.Children.RemoveAt(_stackPanel.Children.Count-1); NavigatedEventHandler dispose = null; dispose = (o, args) => { browser.Navigated -= dispose; browser.Dispose(); }; browser.Navigated += dispose; browser.Navigate(new Uri("about:blank")); } 

Prova questa soluzione, so che non è l’ideale. Incolla il codice dopo ogni caricamento della pagina

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