Ho riscontrato problemi quando i controlli che allocavano grandi quantità di memoria venivano distrutti e ricostruiti utilizzando gli eventi Input dall'interfaccia utente.Come può una memoria libera utilizzata da pesanti controlli WPF in modo deterministico?
Ovviamente, l'impostazione di un contenuto della finestra su null non è sufficiente per liberare la memoria utilizzata dal controllo contenuto. Alla fine sarà GCed. Ma come gli eventi di input per distruggere e costruire i controlli diventano più frequenti, il GC sembra saltare alcuni oggetti.
ho rotto giù a questo esempio:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseMove">
</Window>
C#:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private class HeavyObject : UserControl
{
private byte[] x = new byte[750000000];
public HeavyObject()
{
for (int i = 0; i++ < 99999999;) { } // just so we can follow visually in Process Explorer
Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
}
}
public MainWindow()
{
InitializeComponent();
//Works 1:
//while (true)
//{
// window.Content = null;
// window.Content = new HeavyObject();
//}
}
private void window_MouseMove(object sender, MouseEventArgs e)
{
if (window.Content == null)
{
GC.Collect();
GC.WaitForPendingFinalizers();
// Works 2:
//new HeavyObject();
//window.Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
//return;
window.Content = new HeavyObject();
}
else
{
window.Content = null;
}
}
}
}
Questo fa allocare un oggetto di 750 MB ~ (HeavyObject) ad ogni evento MouseMove e mettere è il contenuto della finestra principale. Se il Cursore viene tenuto fermo sulla finestra, la modifica del contenuto riattiva all'infinito l'evento. Il riferimento a HeavyObject viene annullato prima della sua successiva costruzione e viene attivato un inutile tentativo di GC sui suoi resti.
Quello che possiamo vedere in ProcessExplorer/Taskman è che a volte ci sono almeno 1,5 GB allocati. In caso contrario, muovi il mouse selvaggiamente e lascia e rientra nella finestra. Se compili per x86 senza LARGEADDRESSAWARE, riceverai invece una OutOfMemoryException (limite ~ 1,3 GB).
Non si verificherà questo comportamento se si alloca l'oggetto HeavyObject in un ciclo infinito senza utilizzare l'input del mouse (sezione uncomment "Works 1") o se si attiva l'allocazione mediante l'input del mouse ma non si inserisce l'oggetto nel albero visivo (uncomment seciton "Works 2").
Quindi suppongo che abbia qualcosa a che fare con l'albero visuale liberando pigramente le risorse. Ma poi c'è un altro strano effetto: se si consumano 1,5 GB durante il cursore fuori dalla finestra, sembra che il GC non esegua il kick in finché MouseMove non viene attivato di nuovo. Almeno, il consumo di memoria sembra stabilizzarsi finché non vengono attivati ulteriori eventi. Quindi o c'è un riferimento longevo lasciato da qualche parte o GC diventa pigro quando non c'è attività.
Sembra abbastanza strano per me. Riesci a capire cosa sta succedendo?
Edit: Come ha commentato BalamBalam: I dati dietro il controllo potrebbe essere dereferenziati prima di distruggere il controllo. Quello sarebbe stato il piano B. Tuttavia forse c'è una soluzione più generica.
Dire che tali controlli sono codici errati non è utile. Mi piacerebbe sapere perché un oggetto non tenga alcun riferimento a GCed se lascio l'interfaccia utente da solo per alcune zecche dopo averlo dereferenziato, ma indugia per sempre se un altro (che deve essere stato creato dall'input dell'utente) prende immediatamente il suo posto.
Non riesco a capire quale sia il tuo problema ... presumendo che il codice pazzo che hai qui sia solo per test. Ma hai imparato una cosa: i tentativi di forzare GC spesso non fanno quello che pensi che faranno. –
Il codice è un esempio, contenente due soluzioni alternative in cui il problema non si verificherà. Giusto per limitare le presunzioni. Il problema è: avere un grande controllo come contenuto della finestra -> impostare il nuovo grande controllo come contenuto della finestra ** con l'input del mouse ** -> più di un grande controllo vivo in una sola volta. –
Beh, spero solo che nessuno possa allocare un oggetto da 750 MB su una mossa del mouse. Questo è uno scenario molto irrealistico, e quindi tutto il tuo fondamento qui è shakey. –