2009-12-07 11 views
13

Ho un'applicazione WPF basata su PRISM che utilizza il modello MVVM.Qual è il modo migliore per evitare le perdite di memoria in un'applicazione WPF PRISM/MVVM

ho notato che di tanto in tanto i miei modelli vista, opinioni e tutto ciò che ad essi si bloccherà in giro per molto tempo dopo la loro durata di vita prevista.

Una perdita implicava la sottoscrizione a CollectionChanged su una raccolta appartenente a un servizio inserito, un altro non richiedeva il metodo Stop su DispatcherTimer, e l'altro richiedeva che una raccolta venisse cancellata dai suoi articoli.

Mi sento di usare un oggetto CompositePresentationEvent è preferibile iscriversi a CollectionChanged, ma negli altri scenari mi propongo di implementare IDisposable e le viste chiamano il metodo Dispose sui modelli di visualizzazione.

Ma poi qualcosa deve dire la vista quando a chiamare Dispose sul modello vista, che diventa ancora meno attraente quando la complessità dei punti di vista ad aumentare, e cominciano incluse le viste del bambino.

Cosa pensi sia l'approccio migliore per la manipolazione vista modelli, per assicurarsi che non perdano la memoria?

Grazie in anticipo

Ian

risposta

14

posso dirvi che ho vissuto al 100% del dolore che avete sperimentato. Siamo fratelli di perdita di memoria, penso.

Purtroppo l'unica cosa che ho capito da fare qui è qualcosa di molto simile a quello che stai pensando.

Quello che abbiamo fatto è creare una proprietà collegato che la vista può applicare a se stessa per associare un gestore per il ViewModel:

<UserControl ... 
      common:LifecycleManagement.CloseHandler="{Binding CloseAction}"> 
... 
</UserControl> 

Allora il nostro ViewModel solo ha un metodo su di esso di tipo Azione:

public MyVM : ViewModel 
{ 
    public Action CloseAction 
    { 
      get { return CloseActionInternal; } 
    } 

    private void CloseActionInternal() 
    { 
      //TODO: stop timers, cleanup, etc; 
    } 
} 

Quando i miei metodo close incendi (che hanno un paio di modi per fare questo ... è un'interfaccia utente TabControl con "X" sulle intestazioni scheda, quel genere di cose), ho semplicemente controllare per vedere se questo punto di vista si è registrato con AttachedProperty. Se è così, chiamo il metodo a cui ci si riferisce.

E 'un modo piuttosto rotonda di semplice controllo per vedere se il DataContext di una View è un IDisposable, ma si sentiva meglio al momento. Il problema con il controllo di DataContext è che potresti avere modelli di visualizzazione secondaria che necessitano anche di questo controllo. Dovresti assicurarti che i tuoi modellini di vista portino avanti questa chiamata di blocco o controlla tutte le visualizzazioni nel grafico e verifica se i loro datacontesti sono IDisposable (ugh).

Mi sento come se ci fosse qualcosa che manca qui. Ci sono alcuni altri framework che tentano di mitigare questo scenario in altri modi. Potresti dare un'occhiata a Caliburn. Ha un sistema per la gestione di questo in cui un ViewModel è a conoscenza di tutti i modelli di visualizzazione secondaria e questo consente di collegare automaticamente le cose in avanti. In particolare, esiste un'interfaccia chiamata ISupportCustomShutdown (penso che sia quello che viene chiamato) che aiuta a mitigare questo problema.

La cosa migliore che ho fatto, tuttavia, è assicurarsi e utilizzare strumenti di perdita di memoria come Redgate Memory Profiler che consentono di visualizzare il grafico dell'oggetto e trovare l'oggetto radice. Se sei stato in grado di identificare il problema di DispatchTimer, immagino che lo stai già facendo.

Modifica: Ho dimenticato una cosa importante. C'è una potenziale perdita di memoria causata da uno dei gestori di eventi in DelegateCommand. Ecco una discussione su Codeplex che la spiega. http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065

L'ultima versione di Prism (v2.1) ha risolto questo problema. (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en).

+0

LOL, È un buco profondo in cui mi sto scavando qui;) Ero curioso di sapere su cosa è collegato il tuo comportamento collegato a UserControl, ho cercato un modo per rilevare quando la vista è stata interrotta. Unloaded sembra la scelta più ovvia, ma non è di grande aiuto perché si attiva ogni volta che la vista diventa inattiva –

+0

Ah è personalizzato.Ogni volta che una vista deve chiuderla, chiamerà un metodo su un insieme di comandi dell'applicazione che abbiamo. CloseView (vista oggetto). Controllo con lo store dietro la proprietà allegata per vedere se quella vista o una delle sue viste secondarie sono registrate per avere gestori di chiusura. Se è così, chiamo il metodo che hanno registrato. L'unica magia qui è l'uso delle classi LogicalTreeHelper e VisualTreeHelper per individuare le viste secondarie. http://msdn.microsoft.com/en-us/library/system.windows.media.visualtreehelper.aspx –

+0

Abbiamo provato anche a scaricare, btw. In alcuni casi era ok ... avremmo fermato i timer che essenzialmente erano solo dati di aggiornamento e non c'era alcun motivo per aggiornarlo se la vista non era visibile (come se fosse su schede o qualcosa del genere), ma a volte tu ho solo bisogno di un timer per non fermarsi mai a meno che la vista non venga uccisa e questa cosa esplicita è la cosa migliore che siamo riusciti a trovare. –

2

I miei risultati finora ...

Oltre a PRISM, Unità, WPF e MVVM ci sono anche utilizzando Entity Framework e la griglia di dati Xceed. Il profiling della memoria è stato fatto usando dotTrace.

Ho finito per implementare IDisposable su una classe di base per i miei modelli di vista con il metodo Dispose (bool) che è stato dichiarato virtuale consentendo alle sottoclassi la possibilità di ripulire. Dato che ogni modello di vista nella nostra applicazione ottiene un contenitore figlio da Unity, lo smaltiamo, nel nostro caso questo assicura che l'ObjectContext di EF sia andato fuori campo. Questa era la nostra principale fonte di perdite di memoria.

Il modello di vista è disposto all'interno di un metodo CloseView (UserControl) esplicito su una classe di controller di base. Cerca un IDisposable sul DataContext della vista e chiama Dispose su di esso.

La griglia di dati Xceed sembra causare una buona parte delle perdite, specialmente nelle visualizzazioni di lunga durata. Qualsiasi vista che aggiorna l'ItemSource della griglia di dati assigendo una nuova raccolta dovrebbe chiamare Cancella() sulla raccolta esistente prima di assegnarne una nuova.

Fare attenzione a Entity Framework ed evitare qualsiasi contesto di oggetti a esecuzione prolungata. È molto spietato quando si tratta di collezioni di grandi dimensioni, anche se hai rimosso la raccolta se il tracciamento è attivato, conterrà un riferimento a tutti gli elementi della collezione anche se non li hai più appesi.

Se non è necessario aggiornare l'entità, recuperarlo con MergeOption.NoTracking, in particolare nelle viste di lunga durata che si associano alle raccolte.

Evitare visualizzazioni con una lunga durata, non tenerle in una regione quando non sono visibili, questo causerà dolore soprattutto se aggiornano i dati a intervalli regolari quando sono visibili.

Quando si utilizza CellContentTemplates sulla colonna Xceed, non utilizzare le risorse dinamiche poiché la risorsa conterrà un riferimento alla cella, che a sua volta manterrà attiva l'intera vista.

Quando si utilizza CellEditor nella colonna Xceed e la risorsa è memorizzata in un dizionario di risorse esterno, aggiungere x: Shared = "False" alla risorsa contenente CellEditor, ancora una volta la risorsa conterrà un riferimento alla cella, utilizzando x : Shared = "False" garantisce di ottenere una nuova copia ogni volta, con il vecchio rimosso correttamente.

Fare attenzione quando si associa il DelegateCommand agli elementi all'interno della griglia di dati Exceed, se si dispone di un caso come un pulsante di eliminazione sulla riga che si lega a un comando, assicurarsi di cancellare la raccolta contenente ItemsSource prima di chiudere la vista . Se si sta aggiornando la raccolta, è necessario reinizializzare il comando e il comando manterrà un riferimento a ogni riga.

+0

Ciò evidenzia un buon punto che dovrei menzionare. L'ultima versione rilasciata di Prism ha una potenziale perdita di memoria in DelegateCommand. Avrebbe dovuto utilizzare riferimenti deboli per uno dei gestori di eventi che aveva, ma non lo fece. Ho finito per biforcare il codice e correggerlo da solo, ma da allora hanno risolto nella sorgente che è stata controllata nel repository su codeplex. Tirare verso il basso l'ultima fonte e compilare e si dovrebbe evitare anche questo. –

Problemi correlati