Domanda

Ho creato un "comportamento allegato" nella mia applicazione WPF che mi consente di gestire la pressione del tasto Invio e passare al controllo successivo.Lo chiamo EnterKeyTraversal.IsEnabled e puoi vedere il codice sul mio blog Qui.

La mia preoccupazione principale ora è che potrei avere una perdita di memoria, poiché sto gestendo l'evento PreviewKeyDown su UIElements e non "sgancio" mai esplicitamente l'evento.

Qual è l'approccio migliore per prevenire questa perdita (se effettivamente ce n'è una)?Dovrei tenere un elenco degli elementi che sto gestendo e sganciare l'evento PreviewKeyDown nell'evento Application.Exit?Qualcuno ha avuto successo con i comportamenti allegati nelle proprie applicazioni WPF e ha trovato un'elegante soluzione di gestione della memoria?

È stato utile?

Soluzione

Non sono d'accordo con DannySmurf

Alcuni oggetti di layout WPF possono ostruire la memoria e rallentare notevolmente l'applicazione quando non vengono raccolti. Quindi trovo che la scelta delle parole sia corretta, stai perdendo memoria negli oggetti che non usi più. Ti aspetti che gli oggetti vengano raccolti in modo inutile, ma non lo sono, perché c'è un riferimento da qualche parte (in questo caso nel da un gestore di eventi).

Ora per una vera risposta :)

Ti consiglio di leggere questo Articolo sulle prestazioni WPF su MSDN

  

Non rimuovere i gestori di eventi sugli oggetti   può mantenere in vita gli oggetti

     

Il delegato a cui passa un oggetto   il suo evento è effettivamente un riferimento   a quell'oggetto. Pertanto, evento   i gestori possono mantenere gli oggetti in vita più a lungo   del previsto. Quando si esegue pulito   di un oggetto che si è registrato a   ascolta l'evento di un oggetto, lo è   essenziale per rimuovere quel delegato   prima di rilasciare l'oggetto. conservazione   gli oggetti non necessari vivi aumentano il   utilizzo della memoria dell'applicazione. Questo è   particolarmente vero quando l'oggetto è il   radice di un albero logico o visivo   albero.

Ti consiglia di esaminare Pattern di eventi deboli

Un'altra soluzione sarebbe quella di rimuovere i gestori di eventi quando hai finito con un oggetto. Ma so che con Proprietà collegate quel punto potrebbe non essere sempre chiaro ..

Spero che questo aiuti!

Altri suggerimenti

Dibattito filosofico a parte, guardando il post sul blog dell'OP, non vedo alcuna perdita qui:

ue.PreviewKeyDown += ue_PreviewKeyDown;

Un riferimento rigido a ue_PreviewKeyDown è archiviato in ue.PreviewKeyDown .

ue_PreviewKeyDown è un metodo STATIC e non può essere GCed .

Non viene memorizzato alcun riferimento rigido a ue , quindi nulla gli impedisce di essere GCed .

Quindi ... Dov'è la perdita?

Sì, lo so che ai vecchi tempi le perdite di memoria sono un argomento completamente diverso. Ma con il codice gestito, il nuovo significato del termine Perdita di memoria potrebbe essere più appropriato ...

Microsoft riconosce persino che si tratta di una perdita di memoria:

  

Perché implementare il modello WeakEvent?

     

L'ascolto di eventi può portare a   perdite di memoria. La tecnica tipica   per ascoltare un evento è usare   la sintassi specifica della lingua che   associa un gestore a un evento su a   fonte. Ad esempio, in C #, quello   la sintassi è: source.SomeEvent + = new   SomeEventHandler (MyEventHandler).

     

Questa tecnica crea un forte   riferimento dalla sorgente dell'evento a   listener di eventi. Di solito, attaccando   causa un gestore di eventi per un ascoltatore   l'ascoltatore di avere un oggetto   vita influenzata dall'oggetto   durata della fonte (a meno che il   il gestore eventi viene rimosso esplicitamente).   Ma in determinate circostanze potresti   desidera la durata dell'oggetto di   l'ascoltatore deve essere controllato solo da   altri fattori, come ad esempio se   attualmente appartiene all'albero visivo   dell'applicazione e non dal   durata della fonte. Ogni volta che il   la durata dell'oggetto sorgente si estende oltre   la durata dell'oggetto dell'ascoltatore,   il normale modello di eventi porta a   perdita di memoria: l'ascoltatore viene mantenuto   vivo più a lungo del previsto.

Usiamo WPF per un'app client con ToolWindows di grandi dimensioni che può essere trascinata, tutte le cose intelligenti e tutte compatibili con un XBAP .. Ma abbiamo avuto lo stesso problema con alcune ToolWindows che non erano state spazzate via. Ciò era dovuto al fatto che dipendeva ancora dai listener di eventi. Ora questo potrebbe non essere un problema quando chiudi la finestra e chiudi l'app. Ma se stai creando ToolWindows molto grandi con molti comandi, e tutti questi comandi vengono rivalutati più e più volte e le persone devono usare la tua applicazione tutto il giorno .. Posso dirti che ... ostruisce davvero la tua memoria e i tempi di risposta della tua app ..

Inoltre, trovo molto più facile spiegare al mio manager che abbiamo una perdita di memoria, piuttosto che spiegargli che alcuni oggetti non sono spazzatura raccolti a causa di alcuni eventi che devono essere puliti;)

@Nick Sì, la cosa con comportamenti collegati è che per definizione non sono nello stesso oggetto degli elementi di cui stai gestendo gli eventi.

Penso che la risposta risieda nell'utilizzare WeakReference in qualche modo, ma non ho visto alcun semplice esempio di codice per spiegarmelo. :)

Hai mai pensato di implementare il modello di eventi deboli " invece di eventi regolari?

  1. Pattern eventi deboli in WPF
  2. Weak Event Patterns (MSDN)

Per spiegare il mio commento sul post di John Fenton ecco la mia risposta. Vediamo il seguente esempio:

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();

        a.Clicked += b.HandleClicked;
        //a.Clicked += B.StaticHandleClicked;
        //A.StaticClicked += b.HandleClicked;

        var weakA = new WeakReference(a);
        var weakB = new WeakReference(b);

        a = null;
        //b = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.WriteLine("a is alive: " + weakA.IsAlive);
        Console.WriteLine("b is alive: " + weakB.IsAlive);
        Console.ReadKey();
    }


}

class A
{
    public event EventHandler Clicked;
    public static event EventHandler StaticClicked;
}

class B
{
    public void HandleClicked(object sender, EventArgs e)
    {
    }

    public static void StaticHandleClicked(object sender, EventArgs e)
    {
    }
}

Se hai

a.Clicked += b.HandleClicked;

e imposta solo b su null entrambi i riferimenti weakA e weakB rimangono in vita! Se imposti solo a su null b rimane attivo ma non a (il che dimostra che John Fenton ha torto affermando che un riferimento rigido è memorizzato nel provider di eventi - in questo caso a).

Questo mi ha portato alla conclusione SBAGLIATA che

a.Clicked += B.StaticHandleClicked;

porterebbe a una perdita perché ho pensato che l'istanza di a fosse mantenuta dal gestore statico. Questo non è il caso (prova il mio programma). Nel caso del gestore di eventi statici o degli eventi, è il contrario. Se scrivi

A.StaticClicked += b.HandleClicked;

verrà mantenuto un riferimento a b.

Assicurati che gli elementi di riferimento agli eventi siano presenti nell'oggetto a cui fanno riferimento, come caselle di testo nel controllo del modulo. O se ciò non può essere prevenuto. Creare un evento statico su una classe di supporto globale e quindi monitorare la classe di supporto globale per gli eventi. Se questi due passaggi non possono essere eseguiti, prova a utilizzare un WeakReference, in genere sono perfetti per queste situazioni, ma vengono con un sovraccarico.

Ho appena letto il tuo post sul blog e penso che tu abbia ricevuto un consiglio un po 'fuorviante, Matt. Se c'è una effettiva memoria perdita qui, allora questo è un bug in .NET Framework e non qualcosa che puoi necessariamente correggere nel tuo codice.

Quello che penso tu (e il poster sul tuo blog) stia effettivamente parlando qui non è in realtà una perdita, ma piuttosto un consumo continuo di memoria. Non è la stessa cosa. Per essere chiari, la memoria trapelata è la memoria riservata da un programma, quindi abbandonata (ovvero, un puntatore viene lasciato penzolare) e che successivamente non può essere liberato. Poiché la memoria è gestita in .NET, ciò è teoricamente impossibile. È possibile, tuttavia, che un programma riservi una quantità sempre maggiore di memoria senza consentire ai riferimenti di uscire dal campo di applicazione (e diventare idonei alla raccolta dei rifiuti); tuttavia quella memoria non è trapelata. Il GC lo restituirà al sistema una volta terminato il programma.

. Per rispondere alla tua domanda, non penso che tu abbia effettivamente un problema qui. Certamente non hai una perdita di memoria, e dal tuo codice, non penso che tu debba preoccuparti, anche per quanto riguarda il consumo di memoria. Fintanto che ti assicuri che non stai assegnando ripetutamente quel gestore di eventi senza mai disassegnarlo (cioè, che lo hai impostato solo una volta o che lo rimuovi esattamente una volta per ogni volta che lo assegni), che sembra che tu stia facendo, il tuo codice dovrebbe andare bene.

Sembra che sia questo il consiglio che il poster sul tuo blog stava cercando di darti, ma ha usato quell'allarmante lavoro "perdita", " che è una parola spaventosa, ma di cui molti programmatori hanno dimenticato il vero significato nel mondo gestito; non si applica qui.

@Arcturus:

  

... intasa la tua memoria e crea la tua   applicazione davvero lenta quando lo sono   non immondizia raccolta.

Questo è accecantemente ovvio e non sono in disaccordo. Tuttavia:

  

... stai perdendo memoria nell'oggetto   che non usi più ... perché   c'è un riferimento a loro.

"la memoria viene allocata a un programma e quel programma successivamente perde la possibilità di accedervi a causa di difetti logici del programma" (Wikipedia, " Perdita di memoria ")

Se esiste un riferimento attivo a un oggetto a cui il tuo programma può accedere, per definizione non perde memoria. Una perdita indica che l'oggetto non è più accessibile (all'utente o al sistema operativo / al framework) e non verrà liberato per la durata della sessione corrente del sistema operativo . Questo non è il caso qui.

(Mi dispiace essere un nazista semantico ... forse sono un po 'vecchia scuola, ma la perdita ha un significato molto specifico. La gente tende ad usare "perdita di memoria" in questi giorni per significare tutto ciò che consuma più 2 KB di memoria di quello che vogliono ...)

Ma ovviamente, se non si rilascia un gestore di eventi, l'oggetto a cui è collegato non verrà liberato fino a quando la memoria del processo non verrà recuperata dal Garbage Collector all'arresto. Ma questo comportamento è del tutto previsto, contrariamente a quanto sembra implicare. Se ti aspetti che un oggetto venga recuperato, devi rimuovere tutto ciò che può mantenere in vita il riferimento, inclusi i gestori di eventi.

True true,

Hai ragione ovviamente .. Ma c'è un'intera nuova generazione di programmatori che sta nascendo in questo mondo che non toccherà mai il codice non gestito, e credo che le definizioni linguistiche si reinventeranno continuamente. Le perdite di memoria in WPF sono in questo modo diverse rispetto a C / Cpp.

O ovviamente ai miei manager l'ho definito perdita di memoria ... ai miei colleghi l'ho definito un problema di prestazioni!

Riferendosi al problema di Matt, potrebbe essere un problema di prestazioni che potresti dover affrontare. Se usi solo alcune schermate e crei i singoli controlli dello schermo, potresti non vedere affatto questo problema;).

Bene che (il bit manager) posso certamente capire e simpatizzare con.

Ma qualunque cosa la chiami Microsoft, non credo che un "nuovo" la definizione è appropriata. È complicato, perché non viviamo in un mondo gestito al 100% (anche se a Microsoft piace fingere di farlo, Microsoft stessa non vive in un mondo simile). Quando dici perdita di memoria, potresti voler dire che un programma sta consumando troppa memoria (che è la definizione di un utente) o che un riferimento gestito non verrà liberato fino all'uscita (come qui) o che un riferimento non gestito non viene pulito correttamente up (sarebbe una vera perdita di memoria) o quel codice non gestito chiamato dal codice gestito perde la memoria (un'altra vera perdita).

In questo caso, è ovvio quale "perdita di memoria" significa, anche se siamo imprecisi. Ma diventa terribilmente noioso parlare con alcune persone, che chiamano ogni eccesso di consumo o incapacità di raccogliere una perdita di memoria; ed è frustrante quando queste persone sono programmatori, che presumibilmente sanno meglio. È un po 'importante per i termini tecnici avere significati inequivocabili, penso. Il debug è molto più semplice quando lo fanno.

In ogni caso. Non intendo trasformare questo in una discussione ariosa sul linguaggio. Sto solo dicendo ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top