Ottimizzazione delle prestazioni: comportamento degli oggetti
La comprensione del comportamento intrinseco degli oggetti WPF facilita l'individuazione del compromesso ideale tra funzionalità e prestazioni.
Nel presente argomento sono contenute le seguenti sezioni.
- La mancata rimozione dei gestori eventi dagli oggetti può mantenere gli oggetti attivi
- Proprietà e oggetti di dipendenza
- Oggetti Freezable
- Virtualizzazione dell'interfaccia utente
- Argomenti correlati
La mancata rimozione dei gestori eventi dagli oggetti può mantenere gli oggetti attivi
Il delegato passato da un oggeto al relativo evento è un riferimento effettivo a tale oggetto. Di conseguenza, i gestori eventi possono mantenere gli oggetti attivi più a lungo del previsto. Quando si esegue la pulitura di un oggetto registrato per restare in ascolto di un evento dell'oggetto, è essenziale rimuovere tale delegato prima di rilasciare l'oggetto. Mantenere attivi oggetti non necessari aumenta l'utilizzo di memoria dell'applicazione. Questa situazione si verifica soprattutto quando l'oggetto è la radice di un albero logico o di una struttura ad albero visuale.
In WPF viene introdotto un modello di listener di eventi debole per eventi che possono rivelarsi utili in situazioni in cui è difficile tenere traccia delle relazioni di durata degli oggetti tra origine e listener. Alcuni eventi WPF esistenti utilizzano questo modello. Questo modello può essere utile per implementare oggetti con eventi personalizzati. Per informazioni dettagliate, vedere Modelli di eventi deboli.
Sono disponibili vari strumenti, ad esempio il profiler CLR Profiler e il visualizzatore del working set, in grado di fornire informazioni sull'utilizzo della memoria di un determinato processo. Il profiler CLR include numerose visualizzazioni del profilo di allocazione utili, tra cui un istogramma dei tipi allocati, grafici delle allocazioni e delle chiamate, una cronologia che mostra le operazioni di Garbage Collection di varie generazioni e lo stato dell'heap gestito che ne deriva e una struttura ad albero delle chiamate che mostra le allocazioni per metodo e i caricamenti degli assembly. Per ulteriori informazioni, visitare il sito .NET Framework Developer Center (informazioni in lingua inglese).
Proprietà e oggetti di dipendenza
In genere, l'accesso a una proprietà di dipendeza di un oggetto DependencyObject non è più lento dell'accesso a una proprietà CLR. Sebbene vi sia un leggero sovraccarico delle prestazioni per l'impostazione del valore di una proprietà, ottenere un valore è rapido quanto ottenere il valore da una proprietà CLR. Il sovraccarico delle prestazioni è tuttavia compensato dal fatto che le proprietà di dipendenza supportano funzionalità efficienti, quali l'associazione ai dati, l'animazione, l'ereditarietà e l'applicazione di stili. Per ulteriori informazioni, vedere Cenni preliminari sulle proprietà di dipendenza.
Ottimizzazioni di DependencyProperty
È necessario definire con grande attenzione le proprietà di dipendenza nell'applicazione. Se un oggetto DependencyProperty influisce solo su opzioni di metadati di tipo rendering, anziché su altre opzioni di metadati come AffectsMeasure, è necessario contrassegnarlo come tale eseguendo l'override dei metadati. Per ulteriori informazioni sull'override o su come ottenere i metadati delle proprietà, vedere Metadati della proprietà di dipendenza.
Nel caso in cui non tutte le modifiche delle proprietà influiscono effettivamente sulla misura, la disposizione e il rendering, può essere più efficace disporre di un gestore delle modifiche delle proprietà che invalidi la misura, la disposizione e i passaggi di rendering manualmente. Ad esempio, è possibile decidere di eseguire nuovamente il rendering di uno sfondo solo quando un valore è superiore a un limite impostato. In questo caso, il rendering verrebbe invalidato dal gestore delle modifiche delle proprietà solo quando il valore supera il limite impostato.
Problemi legati alla possibilità di rendere ereditabile un oggetto DependencyProperty
Per impostazione predefinita, le proprietà di dipendenza registrate non sono ereditabili. Tuttavia, è possibile rendere ereditabile qualsiasi proprietà in modo esplicito. Sebbene si tratti di una funzionalità utile, la conversione di una proprietà per renderla ereditabile ha un impatto sulle prestazioni poiché aumenta la durata dell'annullamento della convalida della proprietà.
Utilizzo di RegisterClassHandler
Mentre la chiamata a RegisterClassHandler consente di salvare lo stato dell'istanza, è importante sapere che il gestore viene chiamato per tutte le istanze, con conseguenti problemi di prestazioni. Utilizzare RegisterClassHandler solo quando l'applicazione richiede il salvataggio dello stato dell'istanza.
Impostazione del valore predefinito di un oggetto DependencyProperty durante le registrazione
Quando si crea un oggetto DependencyProperty che richiede un valore predefinito, impostare il valore utilizzando i metadati predefiniti passati come parametro al metodo Register di DependencyProperty. Utilizzare questa tecnica anziché l'impostazione del valore della proprietà in un costruttore o su ciascuna istanza di un elemento.
Impostazione del valore di PropertyMetadata utilizzando Register
Quando si crea un oggetto DependencyProperty, è possibile impostare PropertyMetadata utilizzando i metodi Register o OverrideMetadata. Sebbene l'oggetto possa disporre di un costruttore statico per chiamare OverrideMetadata, non si tratta della soluzione ottimale a causa dell'impatto sulle prestazioni. Per garantire prestazioni ottimali, impostare PropertyMetadata durante la chiamata a Register.
Oggetti Freezable
Freezable è un tipo di oggetto speciale che presenta due stati: non bloccato e bloccato. Bloccare gli oggetti ogni volta che è possibile migliora le prestazioni dell'applicazione e ne riduce il working set. Per ulteriori informazioni, vedere Cenni preliminari sugli oggetti Freezable.
Ogni oggetto Freezable presenta un evento Changed che viene generato a ogni modifica dell'oggetto. Tuttavia, le notifiche delle modifiche sono dispendiose in termini di prestazioni dell'applicazione.
Nell'esempio riportato di seguito ogni Rectangle utilizza 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 l'evento Changed deve essere generato da SolidColorBrush, è necessario richiamare la funzione di callback per ogni Rectangle. L'accumulo di queste chiamate alla funzione di callback causa una riduzione significativa delle prestazioni. Inoltre, aggiungere e rimuovere i gestori a questo punto è molto dispendioso in termini di prestazioni poiché per eseguire tali operazioni l'applicazione deve scorrere tutto l'elenco. Se lo scenario dell'applicazione non modifica mai l'oggetto SolidColorBrush, sulle prestazioni influirà il fatto di conservare gestori per eventi Changed non necessari.
Il blocco di un oggetto Freezable consente di migliorarne le prestazioni poiché evita di dover impiegare risorse nella conservazione delle notifiche delle modifiche. Nella tabella riportata di seguito vengono illustrate le dimensioni di un semplice oggetto SolidColorBrush quando la relativa proprietà IsFrozen è impostata su true, rispetto a quando non lo è. Si presuppone l'applicazione di un pennello alla proprietà Fill di dieci Rectangle.
Stato |
Dimensione |
---|---|
Oggetto SolidColorBrush bloccato |
212 byte |
Oggetto SolidColorBrush non bloccato |
972 byte |
Nell'esempio di codice riportato di seguito viene illustrato questo concetto:
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
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;
}
I gestori modificati su oggetti Freezable non bloccati può mantenere gli oggetti attivi
Il delegato passato da un oggetto all'evento Changed di un oggetto Freezable è un riferimento effettivo a tale oggetto. Di conseguenza, i gestori di eventi Changed possono mantenere gli oggetti attivi più a lungo del previsto. Quando si esegue la pulitura di un oggetto registrato per restare in ascolto di un evento Changed dell'oggetto Freezable, è essenziale rimuovere tale delegato prima di rilasciare l'oggetto.
WPF esegue anche l'associazione di eventi Changed internamente. Ad esempio, tutte le proprietà di dipendenza che accettano Freezable come valore rimangono automaticamente in ascolto di eventi Changed. Questo concetto viene illustrato dalla proprietà Fill, che accetta Brush.
Dim myBrush As Brush = New SolidColorBrush(Colors.Red)
Dim myRectangle As New Rectangle()
myRectangle.Fill = myBrush
Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;
Al momento dell'assegnazione di myBrush a myRectangle.Fill, un delegato che punta all'oggetto Rectangle viene aggiunto all'evento Changed dell'oggetto SolidColorBrush. Nel codice riportato di seguito, pertanto, myRect non viene effettivamente reso idoneo per Garbage Collection:
myRectangle = Nothing
myRectangle = null;
In questo caso myBrush mantiene myRectangle attivo e lo richiama al momento della generazione del relativo evento Changed. Si noti che l'assegnazione di myBrush alla proprietà Fill di un nuovo oggetto Rectangle aggiunge semplicemente un altro gestore eventi a myBrush.
Per eseguire la pulitura di questi tipi di oggetti è consigliabile rimuovere Brush dalla proprietà Fill, che a sua volta rimuove il gestore di eventi Changed.
myRectangle.Fill = Nothing
myRectangle = Nothing
myRectangle.Fill = null;
myRectangle = null;
Virtualizzazione dell'interfaccia utente
In WPF è disponibile anche una variante dell'elemento StackPanel che consente di virtualizzare automaticamente il contenuto figlio con associazione a dati. In questo contesto, il termine "virtualizzare" si riferisce a una tecnica grazie alla quale, a partire da un gran numero di elementi dei dati, viene generato un sottoinsieme di oggetti in base agli elementi visibili sullo schermo. Generare un elevato numero di elementi dell'interfaccia utente, quando solo alcuni possono essere visualizzati sullo schermo in un dato momento, richiede un intenso consumo di risorse sia in termini di memoria che di processore. VirtualizingStackPanel, tramite le funzionalità disponibili in VirtualizingPanel, calcola gli elementi visibili e utilizza l'elemento ItemContainerGenerator di un oggetto ItemsControl (ad esempio ListBox o ListView) per creare elementi solo per gli elementi visibili.
Per ottimizzare le 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 visibili. Questa operazione non deve essere confusa con la virtualizzazione dei dati, in cui gli oggetti dati non sono tutti presenti nell'insieme locale, ma inviati nel flusso in base alle esigenze.
Nella tabella riportata di seguito viene indicato il tempo trascorso per l'aggiunta e il rendering di 5000 elementi TextBlock in un oggetto StackPanel e in un oggetto VirtualizingStackPanel. In questo scenario, le misure rappresentano il tempo compreso tra il collegamento di una stringa di testo alla proprietà ItemsSource di un oggetto ItemsControl e il tempo in cui tale stringa di testo viene visualizzata nel riquadro.
Riquadro host |
Durata del rendering (ms) |
---|---|
3210 |
|
46 |
Vedere anche
Concetti
Ottimizzazione delle prestazioni di applicazioni WPF
Pianificazione delle prestazioni dell'applicazione
Ottimizzazione delle prestazioni: sfruttare appieno l'hardware
Ottimizzazione delle prestazioni: layout e progettazione
Ottimizzazione delle prestazioni: grafica bidimensionale e creazione di immagini
Ottimizzazione delle prestazioni: risorse di applicazioni
Ottimizzazione delle prestazioni: testo