Optimieren der Leistung: Objektverhalten

Wenn Sie das Verhalten systeminterner WPF-Objekte verstehen, können Sie Funktionalität und Leistung optimal miteinander vereinen.

Wenn Ereignishandler nicht aus Objekten entfernt werden, bleiben diese möglicherweise aktiv

Der Delegat, den ein Objekt an sein Ereignis übergibt, ist gewissermaßen ein Verweis auf dieses Objekt. Aufgrund von Ereignishandlern können Objekte länger als erwartet aktiv sein. Wenn sie ein Objekt bereinigen, das registriert wurde, um auf ein Ereignis eines Objekts zu lauschen, ist es erforderlich, dass Sie diesen Delegaten entfernen, bevor Sie das Objekt freigeben. Wenn nicht benötigte Objekte weiter aktiv sind, erhöht dies die Speicherauslastung Ihrer Anwendung. Dies ist insbesondere dann der Fall, wenn das Objekt der Stamm einer logischen oder visuellen Struktur ist.

WPF führt ein schwaches Ereignislistenermuster für Ereignisse ein, das dann nützlich sein kann, wenn die Beziehungen der Objektlebensdauer zwischen Quelle und Listener schwierig zu überwachen sind. Einige vorhandene WPF-Ereignisse verwenden dieses Muster. Wenn Sie Objekte mit benutzerdefinierten Ereignissen implementieren, kann dieses Muster für Sie nützlich sein. Weitere Informationen finden Sie unter Schwache Ereignismuster.

Es gibt mehrere Tools, wie z.B. den CLR-Profiler und den Workingset-Viewer, die Informationen zur Speicherauslastung eines angegebenen Prozesses zur Verfügung stellen können. Der CLR-Profiler umfasst eine Reihe sehr nützlicher Ansichten des Belegungsprofils, darunter z.B. ein Histogramm der zugewiesenen Typen, Zuordnungs- und Aufrufdiagramme, eine Zeitachse mit den automatische Speicherbereinigungen verschiedener Generationen und der daraus entstehende Status des verwalteten Heaps nach diesen Bereinigungen sowie eine Aufrufstruktur, die Zuweisungen und Laden von Assemblys für jede Methode veranschaulicht. Weitere Informationen finden Sie unter Leistung.

Anhängigkeitseigenschaften und Objekte

Für gewöhnlich ist der Zugriff auf eine Abhängigkeitseigenschaft eines DependencyObject nicht langsamer als der Zugriff auf eine CLR-Eigenschaft. Während es einen kleinen Leistungsaufwand für das Festlegen eines Eigenschaftswerts gibt, ist das Abrufen eines Werts genauso schnell wie das Abrufen des Werts über die CLR-Eigenschaft. Der kleine Leistungsaufwand wird durch die Tatsache verursacht, dass Abhängigkeitseigenschaften zuverlässige Funktionen unterstützen, wie z.B. die Datenbindung, die Animation, die Vererbung und das Formatieren. Weitere Informationen finden Sie unter Übersicht über Abhängigkeitseigenschaften.

DependencyProperty-Optimierungen

Sie sollten Abhängigkeitseigenschaften in Ihrer Anwendung sorgfältig definieren. Wenn Ihre DependencyProperty nur Rendertyp-Optionen für Metadaten statt anderer Metadatenoptionen wie z. B. AffectsMeasure betrifft, 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.

Möglicherweise ist es effizienter, mit einem Handler für Eigenschaftenänderungen die Messungs-, Anordnungs- und Rendering-Durchläufe manuell für ungültig zu erklären, wenn nicht alle Eigenschaftenänderungen „Messung“, „Anordnung“ und „Rendering“ betreffen. Möglicherweise möchten Sie einen Hintergrund nur dann erneut rendern, wenn ein Wert höher als ein festgelegter Grenzwert ist. In diesem Fall würde Ihr Handler für Eigenschaftenänderungen nur „render“ für ungültig erklären, wenn der festgelegte Grenzwert überschritten wird.

Sie können eine Abhängigkeitseigenschaft nicht frei vererben

Registrierte Abhängigkeitseigenschaften sind standardmäßig nicht vererbbar. Allerdings können Sie eine Eigenschaft explizit vererbbar machen. Obwohl dies nützlich ist, beeinträchtigt das Konvertieren einer Eigenschaft in eine vererbbare Eigenschaft die Leistung, da die Zeit für das Aufheben der Validierung erhöht wird.

Verwenden Sie RegisterClassHandler mit Vorsicht

Obwohl der Aufruf von RegisterClassHandler Ihnen das Speichern des Instanzzustands ermöglicht, ist es wichtig, dass Ihnen bewusst ist, dass der Handler für jede Instanz aufgerufen wird, wodurch es zu Leistungsproblemen kommen kann. Verwenden Sie RegisterClassHandler nur, wenn Ihre Anwendung das Speichern des Instanzzustands erfordert.

Legen Sie den Standardwert für eine Abhängigkeitseigenschaft während der Registrierung fest

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. Nutzen Sie diese Vorgehensweise, statt den Eigenschaftswert in einem Konstruktor oder in jeder Instanz des Elements festzulegen.

Festlegen des Werts der Metadaten der Eigenschaft mit der Register-Methode

Bei der Erstellung von DependencyProperty haben Sie die Möglichkeit, PropertyMetadata entweder mit der Methode Register oder OverrideMetadata festzulegen. Obwohl Ihr Objekt eventuell über einen statischen Konstruktor verfügt, der OverrideMetadata aufrufen kann, stellt dies keine optimale Lösung dar und wirkt sich zudem auf die Leistung aus. Für eine optimale Leistung legen Sie PropertyMetadata während des Aufrufs auf Register fest.

Freezable-Objekte

Ein Freezable ist eine besondere Art von Objekt, das zwei Zustände hat: nicht fixiert und fixiert. Wenn Sie Objekte immer dann, wenn es möglich ist, fixieren, wird dadurch die Leistung Ihrer Anwendung verbessert und ihr Workingset reduziert. Weitere Informationen finden Sie unter der Übersicht über Freezable-Objekte.

Jedes Freezable hat ein Changed-Ereignis, das ausgelöst wird, wenn es sich ändert. Änderungsbenachrichtigungen nehmen jedoch einiges an Anwendungsleistung in Anspruch.

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

Standardmäßig stellt WPF einen Ereignishandler für das Changed-Ereignis des SolidColorBrush-Objekts zur Verfügung, um die Fill-Eigenschaft des Rectangle-Objekts ungültig zu machen. In diesem Fall muss die SolidColorBrush die Rückruffunktion für jedes Changed jedes Mal abrufen, wenn sie ihr Rectangle-Ereignis auslöst. Die Anhäufung dieser Aufrufe der Rückruffunktion führt zu deutlichen Leistungseinbußen. Darüber hinaus ist es sehr ressourcenintensiv, Handler an diesem Punkt hinzuzufügen und zu entfernen, da die Anwendung dazu die gesamte Liste durchlaufen müssten. Wenn in Ihrem Anwendungsszenario die SolidColorBrush-Klasse nie geändert wird, müssen Sie die Konsequenzen für die unnötige Wartung von Changed-Ereignishandlern tragen.

Das Fixieren eines Freezable-Objekts kann dessen Leistung verbessern, da es keine Ressourcen für die Verwaltung Änderungsbenachrichtigungen mehr aufwenden muss. Die folgende Tabelle zeigt die Größe einer einfachen SolidColorBrush, wenn deren IsFrozen-Eigenschaft auf true festgelegt ist, und wenn deren Eigenschaft nicht festgelegt ist. Dies setzt voraus, dass Sie einen Pinsel auf die Fill-Eigenschaft von zehn Rectangle-Objekten anwenden.

State Größe
SolidColorBrush fixiert 212 Bytes
SolidColorBrush nicht fixiert 972 Bytes

Im folgenden Codebeispiel wird dieses Konzept veranschaulicht:

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

Durch geänderte Handler auf nicht fixierten Freezable-Objekte bleiben Objekte möglicherweise aktiv

Der Delegat, den ein Objekt an das Changed-Ereignis eines Freezable-Objekts übergibt, ist effektiv ein Verweis auf dieses Objekt. Aufgrund von Changed-Ereignishandlern können Objekte länger als erwartet aktiv sein. Wenn ein Objekt bereinigt wird, das registriert wurde, um auf ein Changed-Ereignis eines Freezable-Objekts zu lauschen, ist es erforderlich, dass Sie diesen Delegaten entfernen, bevor Sie das Objekt freigeben.

WPF bindet Changed-Ereignisse auch intern ein. Beispielsweise lauschen alle Abhängigkeitseigenschaften, die Freezable als Wert akzeptieren, automatisch auf Changed-Ereignisse. Die Fill-Eigenschaft, die Brush verwendet, veranschaulicht dieses Konzept.

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

Bei der Zuweisung von myBrush zu myRectangle.Fill wird dem Changed-Ereignis des SolidColorBrush-Objekts ein Delegat hinzugefügt, der auf das Rectangle-Objekt zurückverweist. Dies bedeutet, dass folgender Code myRect nicht zur automatischen Speicherbereinigung berechtigt:

myRectangle = null;
myRectangle = Nothing

In diesem Fall bleibt myRectangle durch myBrush aktiv und führt einen Rückruf aus, wenn es sein Changed-Ereignis auslöst. Beachten Sie, dass, wenn Sie myBrush der Fill-Eigenschaft eines neuen Rectangle zuweisen, myBrush einfach ein weiterer Ereignishandler hinzugefügt wird.

Es wird empfohlen, diese Arten von Objekten zu bereinigen, indem Sie Brush aus der Fill-Eigenschaft entfernen, wodurch wiederum der Changed-Ereignishandler entfernt wird.

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

Virtualisierung der Benutzeroberfläche

WPF stellt außerdem eine Variante des StackPanel-Elements zur Verfügung, das automatisch datengebundenen untergeordneten Inhalt „virtualisiert“. In diesem Zusammenhang bezieht sich das Wort „virtualisieren“ auf eine Technik, mit der eine Teilmenge von Objekten aus einer größeren Menge von Datenelementen generiert wird, je nachdem, welche Elemente auf dem Bildschirm sichtbar sind. Es ist sowohl für den Arbeitsspeicher als auch für den Prozessor sehr aufwändig, eine große Menge an UI-Elementen zu generieren, wenn sich möglicherweise nur sehr wenige Elemente zu einer angegebenen Zeit auf einem Bildschirm befinden. VirtualizingStackPanel (durch von VirtualizingPanel bereitgestellte Funktionalität) berechnet sichtbare Elemente und funktioniert mit ItemContainerGenerator von ItemsControl (z. B ListBox oder ListView), um nur Elemente für sichtbare Objekte zu erstellen.

Zur Leistungsoptimierung werden visuelle Objekte für diese Elemente nur generiert oder aktiv gehalten, wenn sie auf dem Bildschirm sichtbar sind. Wenn sie sich nicht mehr im sichtbaren Bereich des Steuerelements befinden, können die visuellen Objekte entfernt werden. Dies ist nicht zu verwechseln mit Datenvirtualisierung, bei der Datenobjekte nicht alle in der lokalen Auflistung vorhanden sind, sondern bei Bedarf einfließen.

Die folgende Tabelle zeigt die verstrichene Zeit beim Hinzufügen und Rendern von 5.000 TextBlock-Elementen in ein StackPanel und ein VirtualizingStackPanel. In diesem Szenario repräsentieren die Messwerte die Zeit zwischen dem Anfügen einer Textzeichenfolge an die ItemsSource-Eigenschaft eines ItemsControl-Objekts und dem Anzeigen der Textzeichenfolge durch die Panelelemente.

Hostpanel Renderingzeit (in ms)
StackPanel 3210
VirtualizingStackPanel 46

Weitere Informationen