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:
Correzioni spesso proposte che NON FUNZIONANO:
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:
IKeyboardInputSite è un’interfaccia pubblica e ha un metodo Unregister () , quindi non è necessario utilizzare reflection dopo aver ricevuto un riferimento alla raccolta * _keyboardInputSinkChildren *.
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); }