Optimieren der Leistung: Objektverhalten
Wenn Sie das systeminterne Verhalten von WPF-Objekten kennen, können Sie leichter zwischen Funktionalität und Leistung abwägen.
Dieses Thema enthält folgende Abschnitte.
- Durch nicht entfernte Ereignishandler am Objekt werden die Objekte möglicherweise als aktiv beibehalten
- Abhängigkeitseigenschaften und Objekte
- Freezable-Objekte
- Virtualisierung der Benutzeroberfläche
- Verwandte Abschnitte
Durch nicht entfernte Ereignishandler am Objekt werden die Objekte möglicherweise als aktiv beibehalten
Der Delegat, den ein Objekt an sein Ereignis übergibt, ist gewissermaßen ein Verweis auf dieses Objekt. Deshalb können Ereignishandler Objekte länger als erwartet als aktiv beibehalten. Wenn Sie ein Objekt bereinigen, das zur Überwachung eines Objektereignisses registriert wurde, müssen Sie vor der Freigabe des Objekts den entsprechenden Delegaten entfernen. Als aktiv beibehaltene und nicht mehr benötigte Objekte führen dazu, dass die Anwendung mehr Arbeitsspeicher benötigt. Dies trifft insbesondere dann zu, wenn es sich bei dem Objekt um den Stamm einer logischen Struktur oder einer visuellen Struktur handelt.
WPF führt für Ereignisse ein Muster für schwache Ereignislistener ein, das besonders hilfreich ist, wenn die Objektlebensdauerbeziehungen zwischen Quelle und Listener schwer zu verfolgen sind. Einige vorhandene WPF-Ereignisse verwenden dieses Muster. Wenn Sie Objekte mit benutzerdefinierten Ereignissen implementieren, kann Ihnen dieses Muster von Nutzen sein. Ausführliche Informationen finden Sie unter Schwache Ereignismuster.
Es gibt mehrere Tools, z. B. CLR-Profiler und Workingset-Viewer, die Informationen zur Arbeitsspeichernutzung eines angegebenen Prozesses bereitstellen können. Der CLR-Profiler enthält eine Reihe sehr nützlicher Ansichten des Zuordnungsprofils. Dazu gehören ein Histogramm der zugeordneten Typen, Zuordnungs- und Aufrufdiagramme, eine Zeitleiste mit den Garbage Collections der verschiedenen Generationen und dem resultierenden Status des verwalteten Heaps nach den Collections sowie eine Aufrufstruktur, die die Zuordnungen pro Methode sowie die Assemblyladevorgänge anzeigt. Informationen finden Sie unter .NET Framework Developer Center.
Abhängigkeitseigenschaften und Objekte
Im Allgemeinen ist das Zugreifen auf eine Abhängigkeitseigenschaft von einem DependencyObject nicht langsamer als das Zugreifen auf eine CLR-Eigenschaft. Das Festlegen eines Eigenschaftswerts beansprucht zwar geringfügig mehr Leistung, aber das Abrufen eines Werts erfolgt so schnell wie das Abrufen eines Werts aus einer CLR-Eigenschaft. Der geringfügig höhere Leistungsbedarf wird dadurch ausgeglichen, dass Abhängigkeitseigenschaften robuste Features, z. B. Datenbindung, Animation, Vererbung und Formatierung, unterstützen. Weitere Informationen finden Sie unter Übersicht über Abhängigkeitseigenschaften.
DependencyProperty-Optimierungen
Sie sollten Abhängigkeitseigenschaften in der Anwendung sehr sorgfältig definieren. Wenn sich die DependencyProperty nur auf Rendering-Metadatenoptionen und nicht auf andere Metadatenoptionen auswirkt, z. B. AffectsMeasure, sollten Sie sie entsprechend kennzeichnen, indem Sie ihre Metadaten überschreiben. Weitere Informationen über das Überschreiben oder Abrufen von Eigenschaftenmetadaten finden Sie unter Metadaten für Abhängigkeitseigenschaften.
Wenn sich nicht alle Eigenschaftenänderungen auf Measure, Anordnung und Rendering auswirken, kann es sich als effizienter erweisen, wenn Sie mithilfe eines Handlers für Eigenschaftenänderungen Measure-, Anordnungs- und Renderingläufe außer Kraft setzen. Beispiel: Sie möchten einen Hintergrund nur dann erneut rendern, wenn ein Wert den festgelegten Grenzwert überschreitet. In diesem Fall würde der Handler der Eigenschaftenänderungen das Rendern nur dann außer Kraft setzen, wenn der Wert den festgelegten Grenzwert überschreitet.
DependencyProperty-Vererbung hat Leistungseinbußen zur Folge
Standardmäßig sind registrierte Abhängigkeitseigenschaften nicht vererbbar. Sie können jedoch jede Eigenschaft explizit als vererbbar ausweisen. Obwohl dies ein nützliches Feature ist, wirken sich Eigenschaften, die zu einer vererbbaren Eigenschaft konvertiert wurden, auf die Leistung aus, da die Ungültigkeitserklärung von Eigenschaften mehr Zeit in Anspruch nimmt.
Sorgfältiges Verwenden von RegisterClassHandler
Sie können durch den Aufruf von RegisterClassHandler zwar den Zustand der Instanz speichern, sollten aber beachten, dass der Handler in jeder Instanz aufgerufen wird, was zu Leistungseinbußen führen kann. Verwenden Sie RegisterClassHandler nur dann, wenn die Anwendung das Speichern des Instanzenzustands erfordert.
Festlegen des Standardwerts für eine DependencyProperty während der Registrierung
Wenn Sie eine DependencyProperty erstellen, die einen Standardwert erfordert, legen Sie den Wert mithilfe der Standardmetadaten fest, die als Parameter an die Register-Methode der DependencyProperty übergeben wurden. Verwenden Sie dieses Verfahren, anstatt den Eigenschaftswert in einem Konstruktor oder in jeder Instanz eines Elements festzulegen.
Festlegen des PropertyMetadata-Werts mit Register
Wenn Sie eine DependencyProperty erstellen, können Sie die PropertyMetadata wahlweise anhand der Register-Methode oder der OverrideMetadata-Methode festlegen. Das Objekt könnte zwar zum Aufruf von OverrideMetadata einen statischen Konstruktor verwenden, was jedoch keine optimale Lösung darstellt und Leistungseinbußen zur Folge hat. Um die beste Leistung zu erhalten, legen Sie die PropertyMetadata während des Aufrufs auf Register fest.
Freezable-Objekte
Ein Freezable ist ein besonderer Typ von Objekt, das zwei Zustände aufweisen kann: nicht fixiert und fixiert. Sie sollten Objekte nach Möglichkeit fixieren, um die Leistung der Anwendung zu verbessern und deren Workingset zu reduzieren. Weitere Informationen finden Sie unter Übersicht über Freezable-Objekte.
Jedes Freezable verfügt über ein Changed-Ereignis, das immer dann ausgelöst wird, wenn sich das Objekt ändert. Änderungsbenachrichtigungen sind jedoch im Hinblick auf die Anwendungsleistung aufwendig.
Betrachten Sie das folgende Beispiel, in dem jedes Rectangle das gleiche Brush-Objekt verwendet:
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;
WPF stellt standardmäßig einen Ereignishandler für das Changed-Ereignis des SolidColorBrush-Objekts bereit, um die Fill-Eigenschaft des Rectangle-Objekts außer Kraft zu setzen. In diesem Fall muss das SolidColorBrush-Objekt bei jedem Auslösen des Changed-Ereignisses die Rückruffunktion für jedes Rectangle aufrufen. Die Anhäufung dieser Rückruffunktionsaufrufe führt zu erheblichen Leistungseinbußen. Außerdem ist das Hinzufügen und Entfernen von Handlern zu diesem Zeitpunkt sehr ressourcenintensiv, da die Anwendung dazu die gesamte Liste durchlaufen müsste. Wenn in Ihrem Anwendungsszenario SolidColorBrush nie geändert wird, müssten Sie einen unnötigen Aufwand zur Verwaltung der Changed-Ereignishandler betreiben.
Das Fixieren eines Freezable-Objekts kann zu Leistungsverbesserungen führen, da es keine Ressourcen mehr für die Verwaltung der Änderungsbenachrichtigungen verbrauchen muss. Die folgende Tabelle zeigt die Größe eines einfachen SolidColorBrush-Objekts, dessen IsFrozen-Eigenschaft auf true festgelegt ist, im Vergleich zu einem nicht fixierten Objekt. Dabei wird davon ausgegangen, dass ein Pinsel auf die Fill-Eigenschaft von zehn Rectangle-Objekten angewendet wird.
Zustand |
Größe |
---|---|
SolidColorBrush ist fixiert |
212 Bytes |
SolidColorBrush ist nicht fixiert |
972 Bytes |
Im folgenden Codebeispiel wird dieses Konzept veranschaulicht:
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;
}
Durch geänderte Handler für nicht fixierte Freezable-Objekte werden Objekte möglicherweise als aktiv beibehalten
Der Delegat, den ein Objekt an ein Changed-Ereignis eines Freezable-Objekts übergibt, ist gewissermaßen ein Verweis auf dieses Objekt. Deshalb können Changed-Ereignishandler Objekte länger als erwartet als aktiv beibehalten. Wenn Sie ein Objekt bereinigen, das zur Überwachung des Changed-Ereignisses eines Freezable-Objekts registriert wurde, müssen Sie vor der Freigabe des Objekts den entsprechenden Delegaten entfernen.
WPF nimmt außerdem eine interne Verknüpfung der Changed-Ereignisse vor. Zum Beispiel überwachen alle Abhängigkeitseigenschaften, die Freezable als Wert übernehmen, die Changed-Ereignisse automatisch. Die Fill-Eigenschaft, die einen Brush annimmt, illustriert dieses Konzept.
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;
Bei der Zuordnung von myBrush zu myRectangle.Fill wird ein auf das Rectangle-Objekt zurückweisender Delegat zum Changed-Ereignis des SolidColorBrush-Objekts hinzugefügt. Dies bedeutet, dass im folgenden Code myRect nicht für die Garbage Collection freigegeben wird:
myRectangle = Nothing
myRectangle = null;
In diesem Fall wird durch myBrush das Objekt myRectangle als aktiv beibehalten und erneut aufgerufen, wenn das Changed-Ereignis ausgelöst wird. Beachten Sie, dass die Zuordnung von myBrush zu der Fill-Eigenschaft eines neuen Rectangle-Objekts einfach einen weiteren Ereignishandler an myBrush hinzufügt.
Um diese Objekttypen zu bereinigen, wird empfohlen, dass Sie Brush aus der Fill-Eigenschaft löschen, wodurch wiederum der Changed-Ereignishandler gelöscht wird.
myRectangle.Fill = Nothing
myRectangle = Nothing
myRectangle.Fill = null;
myRectangle = null;
Virtualisierung der Benutzeroberfläche
WPF stellt außerdem eine Variante des StackPanel-Elements bereit, das automatisch datengebundenen untergeordneten Inhalt "virtualisiert". In diesem Zusammenhang bezeichnet "virtualisieren" eine Vorgehensweise, bei der eine Teilmenge von Objekten aus einer größeren Anzahl von Datenelementen generiert wird, wobei berücksichtigt wird, welche Elemente auf dem Bildschirm angezeigt werden. Das Generieren einer großen Anzahl von Benutzeroberflächenelementen nimmt sehr viel Arbeitsspeicher und Prozessorleistung in Anspruch, auch wenn jeweils nur einige der Elemente auf dem Bildschirm angezeigt werden. VirtualizingStackPanel (mithilfe der Funktionen von VirtualizingPanel) berechnet die sichtbaren Elemente und erstellt dann zusammen mit dem ItemContainerGenerator aus einem ItemsControl (z. B. einem ListBox oder einer ListView) nur die Elemente für die sichtbaren Elemente
Im Sinne der Leistungsoptimierung werden visuelle Objekte nur dann für diese Elemente generiert oder als aktiv beibehalten, wenn sie auf dem Bildschirm angezeigt werden. Wenn sie sich nicht mehr im Anzeigebereich des Steuerelements befinden, können die visuellen Objekte entfernt werden. Dies darf nicht mit der Datenvirtualisierung verwechselt werden, bei der nicht alle Datenobjekte in der lokalen Auflistung vorhanden sind, sondern bei Bedarf als Stream geladen werden.
Die folgende Tabelle veranschaulicht die Zeitdauer, die zum Hinzufügen und Rendern von 5000 TextBlock-Elementen in einem StackPanel und einem VirtualizingStackPanel benötigt wird. In diesem Szenario stehen die Messwerte für die Zeit, die ab dem Hinzufügen einer Textzeichenfolge zur ItemsSource-Eigenschaft eines ItemsControl-Objekts bis zu dem Zeitpunkt verstrichen ist, zu dem die Bereichselemente die Textzeichenfolge darstellen.
Hostbereich |
Renderzeit (ms) |
---|---|
3210 |
|
46 |
Siehe auch
Konzepte
Optimieren der WPF-Anwendungsleistung
Optimieren der Leistung: Vorteile der Hardware nutzen
Optimieren der Leistung: Layout und Entwurf
Optimieren der Leistung: 2D-Grafiken und Bildverarbeitung
Optimieren der Leistung: Anwendungsressourcen