Condividi tramite


Ottimizzazione delle prestazioni: comportamento degli oggetti

Comprendere il comportamento intrinseco degli oggetti WPF consente di ottenere i compromessi corretti tra funzionalità e prestazioni.

La mancata rimozione dei gestori eventi sugli oggetti può mantenere attivi gli oggetti

Il delegato che un oggetto passa al relativo evento è effettivamente un riferimento a tale oggetto. Pertanto, i gestori eventi possono mantenere gli oggetti attivi più a lungo del previsto. Quando si esegue la pulizia di un oggetto registrato per ascoltare l'evento di un altro oggetto, è essenziale rimuovere tale delegato prima di rilasciare quell'oggetto. Mantenere in vita oggetti non necessari aumenta l'utilizzo della memoria dell'applicazione. Ciò vale soprattutto quando l'oggetto è la radice di un albero logico o di una struttura ad albero visuale.

WPF introduce un modello di listener di eventi debole che può essere utile nelle situazioni in cui le relazioni di durata degli oggetti tra origine e listener sono difficili da tenere traccia. Alcuni eventi WPF esistenti usano questo modello. Se stai implementando oggetti con eventi personalizzati, questo schema può essere utile per te. Per informazioni dettagliate, vedere modelli di eventi deboli.

Sono disponibili diversi strumenti, ad esempio CLR Profiler e Visualizzatore Working Set, che possono fornire informazioni sull'utilizzo della memoria di un processo specificato. Il CLR Profiler include una serie di visualizzazioni molto utili del profilo di allocazione, tra cui un istogramma dei tipi allocati, grafici di allocazione e chiamata, una linea temporale che mostra le operazioni di garbage collection per varie generazioni e lo stato risultante dell'heap gestito dopo tali raccolte, e un albero delle chiamate che mostra allocazioni per metodo e caricamenti di assembly. Per altre informazioni, vedere Performance.

Proprietà di dipendenza e oggetti

In generale, l'accesso a una proprietà di dipendenza di un DependencyObject non è più lento rispetto all'accesso a una proprietà CLR. Anche se si verifica un piccolo sovraccarico delle prestazioni per l'impostazione di un valore di proprietà, ottenere un valore è altrettanto veloce quanto ottenere il valore da una proprietà CLR. A fronte del leggero sovraccarico delle prestazioni, va considerato che le proprietà di dipendenza supportano potenti funzionalità, come data binding, animazioni, ereditarietà e stili. Per altre informazioni, vedere Panoramica delle proprietà delle dipendenze .

Ottimizzazioni DependencyProperty

È consigliabile definire attentamente le proprietà di dipendenza nell'applicazione. Se il DependencyProperty influisce solo sulle opzioni dei metadati del tipo di rendering, anziché su altre opzioni di metadati, ad esempio AffectsMeasure, devi contrassegnarle come tali eseguendo l'override dei relativi metadati. Per ulteriori informazioni su come eseguire l'override o ottenere i metadati delle proprietà di dipendenza, vedere Metadati delle proprietà di dipendenza.

Potrebbe essere più efficiente disporre di un gestore delle modifiche delle proprietà che invalidi manualmente i processi di misura, disposizione e rendering se non tutte le modifiche alle proprietà influiscono su questi processi. Ad esempio, è possibile decidere di eseguire nuovamente il rendering di uno sfondo solo quando un valore è maggiore di un limite impostato. In questo caso, il gestore delle modifiche delle proprietà invalida il rendering solo quando il valore supera il limite impostato.

Rendere un DependencyProperty ereditabile non è gratuito

Per impostazione predefinita, le proprietà di dipendenza registrate non sono ereditabili. Tuttavia, è possibile rendere ereditabile in modo esplicito qualsiasi proprietà. Anche se si tratta di una funzionalità utile, la conversione di una proprietà per essere ereditabile influisce sulle prestazioni aumentando il periodo di tempo per l'invalidazione della proprietà.

Usare con attenzione RegisterClassHandler

Chiamando RegisterClassHandler per salvare lo stato dell'istanza, è importante sapere che il gestore viene richiamato ad ogni istanza, il che può causare problemi di prestazioni. Usare RegisterClassHandler solo quando l'applicazione richiede di salvare lo stato dell'istanza.

Impostare il valore predefinito per DependencyProperty durante la registrazione

Quando si crea un DependencyProperty che richiede un valore predefinito, impostare il valore usando i metadati predefiniti passati come parametro al metodo Register del DependencyProperty. Usare questa tecnica anziché impostare il valore della proprietà in un costruttore o su ogni istanza di un elemento.

Impostare il valore PropertyMetadata usando Register

Quando si crea un DependencyProperty, è possibile impostare il PropertyMetadata usando i metodi Register o OverrideMetadata. Anche se l'oggetto potrebbe avere un costruttore statico per chiamare OverrideMetadata, questa non è la soluzione ottimale e influirà sulle prestazioni. Per ottenere prestazioni ottimali, settare il PropertyMetadata durante la chiamata a Register.

Oggetti congelabili

Un Freezable è un tipo speciale di oggetto con due stati: scongelato e bloccato. Il blocco di oggetti quando possibile migliora le prestazioni dell'applicazione e riduce il working set. Per ulteriori informazioni, vedere la Panoramica degli Oggetti Freezable .

Ogni Freezable ha un evento Changed generato ogni volta che cambia. Tuttavia, le notifiche di modifica sono costose in termini di prestazioni dell'applicazione.

Si consideri l'esempio seguente in cui ogni Rectangle usa lo stesso oggetto Brush:

rectangle_1.Fill = myBrush;
rectangle_2.Fill = myBrush;
rectangle_3.Fill = myBrush;
// ...
rectangle_10.Fill = myBrush;
rectangle_1.Fill = myBrush
rectangle_2.Fill = myBrush
rectangle_3.Fill = myBrush
' ...
rectangle_10.Fill = myBrush

Per impostazione predefinita, WPF fornisce un gestore eventi per l'evento Changed dell'oggetto SolidColorBrush per invalidare la proprietà Fill dell'oggetto Rectangle. In questo caso, ogni volta che il SolidColorBrush deve generare il relativo evento Changed, è necessario invocare la funzione di callback per ogni Rectangle. L'accumulo di queste invocazioni della funzione di callback impone un impatto significativo sulle prestazioni. Inoltre, è molto impegnativo aggiungere e rimuovere handler a questo punto, poiché l'applicazione deve scorrere l'intero elenco per riuscirci. Se lo scenario dell'applicazione non modifica mai il SolidColorBrush, sosterrà inutilmente il costo per il mantenimento dei gestori di eventi Changed.

Il blocco di un Freezable può migliorare le prestazioni, perché non è più necessario spendere risorse per gestire le notifiche delle modifiche. La tabella seguente mostra le dimensioni di un SolidColorBrush semplice quando la proprietà IsFrozen è impostata su true, rispetto a quando non è impostata. Ciò presuppone di applicare un pennello alla proprietà Fill di dieci oggetti Rectangle.

stato Dimensioni
Frozen SolidColorBrush 212 byte
SolidColorBrush non congelato 972 byte

L'esempio di codice seguente illustra questo concetto:

Brush frozenBrush = new SolidColorBrush(Colors.Blue);
frozenBrush.Freeze();
Brush nonFrozenBrush = new SolidColorBrush(Colors.Blue);

for (int i = 0; i < 10; i++)
{
    // Create a Rectangle using a non-frozed Brush.
    Rectangle rectangleNonFrozen = new Rectangle();
    rectangleNonFrozen.Fill = nonFrozenBrush;

    // Create a Rectangle using a frozed Brush.
    Rectangle rectangleFrozen = new Rectangle();
    rectangleFrozen.Fill = frozenBrush;
}
Dim frozenBrush As Brush = New SolidColorBrush(Colors.Blue)
frozenBrush.Freeze()
Dim nonFrozenBrush As Brush = New SolidColorBrush(Colors.Blue)

For i As Integer = 0 To 9
    ' Create a Rectangle using a non-frozed Brush.
    Dim rectangleNonFrozen As New Rectangle()
    rectangleNonFrozen.Fill = nonFrozenBrush

    ' Create a Rectangle using a frozed Brush.
    Dim rectangleFrozen As New Rectangle()
    rectangleFrozen.Fill = frozenBrush
Next i

I gestori modificati in freezables unfrozen possono mantenere attivi gli oggetti

Il delegato che un oggetto passa all'evento Changed di un oggetto Freezable è effettivamente un riferimento a quell'oggetto. Pertanto, i gestori di eventi Changed possono mantenere gli oggetti attivi più a lungo del previsto. Quando si esegue la pulizia di un oggetto registrato per essere in ascolto dell'evento Changed di un oggetto Freezable, è essenziale rimuovere quel delegato prima di rilasciare l'oggetto.

WPF collega anche internamente Changed eventi. Ad esempio, tutte le proprietà di dipendenza che accettano Freezable come valore ascolteranno automaticamente Changed eventi. La proprietà Fill, che accetta un Brush, illustra questo concetto.

Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;
Dim myBrush As Brush = New SolidColorBrush(Colors.Red)
Dim myRectangle As New Rectangle()
myRectangle.Fill = myBrush

Nell'assegnazione di myBrush a myRectangle.Fill, un delegato che punta all'oggetto Rectangle verrà aggiunto all'evento Changed dell'oggetto SolidColorBrush. Ciò significa che il codice seguente non rende effettivamente idoneo myRect per l'operazione di Garbage Collection:

myRectangle = null;
myRectangle = Nothing

In questo caso myBrush mantiene ancora attivo myRectangle e lo richiama quando genera l'evento Changed. Si noti che l'assegnazione di myBrush alla proprietà Fill di un nuovo Rectangle aggiungerà semplicemente un altro gestore eventi a myBrush.

Il modo consigliato per pulire questi tipi di oggetti consiste nel rimuovere il Brush dalla proprietà Fill, che a sua volta rimuoverà il gestore eventi Changed.

myRectangle.Fill = null;
myRectangle = null;
myRectangle.Fill = Nothing
myRectangle = Nothing

Virtualizzazione interfaccia utente

WPF fornisce anche una variante dell'elemento StackPanel che "virtualizza" automaticamente il contenuto figlio associato a dati. In questo contesto, la parola virtualize fa riferimento a una tecnica in base alla quale un subset di oggetti viene generato da un numero maggiore di elementi di dati in base agli elementi visibili sullo schermo. È intensivo, sia in termini di memoria che di processore, per generare un numero elevato di elementi dell'interfaccia utente quando solo pochi possono trovarsi sullo schermo in un determinato momento. VirtualizingStackPanel (tramite la funzionalità fornita da VirtualizingPanel) calcola gli elementi visibili e funziona con il ItemContainerGenerator da un ItemsControl (ad esempio ListBox o ListView) per creare solo elementi per gli elementi visibili.

Come ottimizzazione delle prestazioni, gli oggetti visivi per questi elementi vengono generati o mantenuti attivi solo se sono visibili sullo schermo. Quando non si trovano più nell'area visualizzabile del controllo, è possibile rimuovere gli oggetti visivi. Questo non deve essere confuso con la virtualizzazione dei dati, in cui gli oggetti dati non sono tutti presenti nella raccolta locale, piuttosto trasmessi in base alle esigenze.

La tabella seguente mostra il tempo impiegato per aggiungere e rendere 5000 elementi TextBlock a un StackPanel e a un VirtualizingStackPanel. In questo scenario, le misurazioni rappresentano il tempo tra l'associazione di una stringa di testo alla proprietà ItemsSource di un oggetto ItemsControl all'ora in cui gli elementi del pannello visualizzano la stringa di testo.

pannello host Tempo di rendering (ms)
StackPanel 3210
VirtualizingStackPanel 46

Vedere anche