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 di gestori eventi dagli oggetti può mantenere gli oggetti attivi

Il delegato passato da un oggetto al relativo evento è di fatto un riferimento all'oggetto. I gestori eventi, quindi, 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 il delegato prima di rilasciare l'oggetto. Mantenere attivi oggetti non necessari aumenta il consumo di memoria dell'applicazione, soprattutto se 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. che può essere utile soprattutto per implementare oggetti con eventi personalizzati. Per informazioni dettagliate, vedere Modelli di eventi deboli.

Sono disponibili vari strumenti, ad esempio il profiler CLR e il visualizzatore del working set, in grado di fornire informazioni sul consumo di memoria di un determinato processo. Il profiler CLR include alcune visualizzazioni del profilo di allocazione molto utili, tra cui un istogramma dei tipi allocati, grafici delle allocazioni e delle chiamate, una cronologia delle 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 altre informazioni, vedere Prestazioni.

Proprietà e oggetti di dipendenza

In generale, l'accesso a una proprietà di dipendenza di un DependencyObject oggetto 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. Il sovraccarico delle prestazioni, infatti, è compensato dalla capacità delle proprietà di dipendenza di supportare funzionalità avanzate come il data binding, l'animazione, l'ereditarietà e l'applicazione di stili. Per altre informazioni, vedere Panoramica sulle proprietà di dipendenza.

Ottimizzazioni di DependencyProperty

È necessario definire le proprietà di dipendenza nell'applicazione con estrema attenzione. Se influisce DependencyProperty solo sulle opzioni dei metadati del tipo di rendering, anziché su altre opzioni di metadati, ad AffectsMeasureesempio , è consigliabile contrassegnarla come tale eseguendo l'override dei relativi metadati. Per altre informazioni sull'override dei metadati delle proprietà o su come ottenere i metadati delle proprietà, vedere Metadati delle proprietà di dipendenza.

Nel caso in cui non tutte le modifiche alle proprietà influiscano effettivamente sulla misura, la disposizione e il rendering, può essere più vantaggioso avere un gestore delle modifiche alle proprietà che consenta di invalidare manualmente la misura, la disposizione e i passaggi di rendering. È possibile, ad esempio, decidere di eseguire nuovamente il rendering di uno sfondo solo se un valore è superiore a un limite impostato. In questo caso, il rendering verrebbe invalidato dal gestore delle modifiche alle proprietà solo se 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. È possibile tuttavia rendere ereditabile qualsiasi proprietà in modo esplicito. Sebbene possa essere utile, la conversione di una proprietà per renderla ereditabile incide sulle prestazioni, poiché aumenta la durata della procedura di annullamento della convalida della proprietà.

Uso corretto di RegisterClassHandler

Anche se la chiamata RegisterClassHandler consente di salvare lo stato dell'istanza, è importante tenere presente che il gestore viene chiamato in ogni istanza, che può causare problemi di prestazioni. Usare RegisterClassHandler solo quando l'applicazione richiede di salvare lo 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 usando i metadati predefiniti passati come parametro al Register metodo di DependencyProperty. Usare questa tecnica, anziché impostare il valore della proprietà, in un costruttore o in ogni istanza di un elemento.

Impostazione del valore PropertyMetadata usando Register

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

Oggetti Freezable

Un Freezable è un tipo speciale di oggetto con due stati: unfrozen e bloccato. Bloccare gli oggetti ogni volta che è possibile migliora le prestazioni dell'applicazione e ne riduce il working set. Per altre informazioni, vedere Cenni preliminari sugli oggetti Freezable.

Ogni Freezable oggetto ha un Changed evento generato ogni volta che cambia. Le notifiche di modifica, tuttavia, sono molto dispendiose in termini di prestazioni dell'applicazione.

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

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 SolidColorBrush dell'oggetto Changed per invalidare la Rectangle proprietà dell'oggetto Fill . In questo caso, ogni volta che l'oggetto SolidColorBrush deve generare il relativo Changed evento, è necessario richiamare la funzione di callback per ogni Rectangle, l'accumulo di queste chiamate di funzione di callback impone una riduzione significativa delle prestazioni. Ma non solo: per aggiungere e rimuovere gestori in questa fase, l'applicazione deve scorrere tutto l'elenco, con un considerevole dispendio di prestazioni. Se lo scenario dell'applicazione non cambia mai , SolidColorBrushsi pagherà il costo di gestione Changed degli eventi inutilmente.

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

Stato Dimensione
Congelato SolidColorBrush 212 byte
Non congelato SolidColorBrush 972 byte

Nell'esempio di codice seguente viene dimostrato 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

Gestori modificati su oggetti Freezable non bloccati possono mantenere gli oggetti attivi

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

WPF associa Changed anche gli eventi internamente. Ad esempio, tutte le proprietà di dipendenza che accettano Freezable come valore ascolteranno Changed automaticamente gli eventi. La Fill proprietà , che accetta , Brushillustra 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 di nuovo all'oggetto Rectangle verrà aggiunto all'evento SolidColorBrush dell'oggetto Changed . Nel codice seguente, quindi, myRect non viene reso idoneo per operazioni di Garbage Collection:

myRectangle = null;
myRectangle = Nothing

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

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

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

Virtualizzazione dell'interfaccia utente

WPF fornisce anche una variante dell'elemento StackPanel che "virtualizza" automaticamente il contenuto figlio associato a dati. In questo contesto il termine virtualizzare si riferisce a una tecnica grazie alla quale, a partire da un numero più elevato di elementi dati, viene generato un subset di oggetti in base agli elementi visibili sullo schermo. La generazione di un elevato numero di elementi dell'interfaccia utente, quando solo alcuni possono essere visualizzati sullo schermo in un momento specifico, richiede un consumo intensivo di risorse sia in termini di memoria che di processore. VirtualizingStackPanel (tramite funzionalità fornite da VirtualizingPanel) calcola gli elementi visibili e funziona con da ItemContainerGenerator un ItemsControl oggetto (ad esempio ListBox o ListView) per creare solo elementi 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, gli oggetti visibili possono essere rimossi. Questa operazione non deve essere confusa con la virtualizzazione dei dati, in cui gli oggetti dati non sono tutti presenti nella raccolta locale, ma trasmessi a seconda delle esigenze.

La tabella seguente mostra il tempo trascorso aggiungendo ed esegue il rendering di 5000 TextBlock elementi a e a .VirtualizingStackPanelStackPanel In questo scenario, le misurazioni rappresentano il tempo tra l'associazione di una stringa di testo alla ItemsSource proprietà di un ItemsControl oggetto all'ora in cui gli elementi del pannello visualizzano la stringa di testo.

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

Vedi anche