Optimisation des performances : comportement d'objets

Comprendre le comportement intrinsèque des objets WPF vous aidera à faire les compromis appropriés entre les fonctionnalités et les performances.

Le fait de ne pas supprimer les gestionnaires d’événements sur les objets permet de conserver les objets actifs

Le délégué qu’un objet passe à son événement est une référence à cet objet. Par conséquent, les gestionnaires d’événements peuvent conserver les objets actifs plus longtemps que prévu. Quand vous nettoyez un objet inscrit pour détecter l’événement d’un objet, il est essentiel de supprimer ce délégué avant de libérer l’objet. La conservation d’objets actifs inutiles augmente l’utilisation de mémoire de l’application. Cela est particulièrement vrai quand l’objet est la racine d’une arborescence logique ou d’une arborescence de visuels.

WPF introduit un modèle d’écouteur d’événements faible pour les événements qui peuvent être utiles dans les situations où les relations de durée de vie des objets entre la source et l’écouteur sont difficiles à suivre. Certains événements WPF existants utilisent ce modèle. Si vous implémentez des objets avec des événements personnalisés, ce modèle peut vous être utile. Pour plus d’informations, consultez Modèles d’événement faible.

Il existe plusieurs outils, comme le profileur CLR et la visionneuse de jeux de travail, qui peuvent fournir des informations sur l’utilisation de mémoire d’un processus spécifié. Le profileur CLR inclut un nombre de vues très utiles du profil d’allocation, notamment un histogramme des types alloués, des graphiques d’allocation et d’appels, une chronologie montrant les opérations de garbage collection de diverses générations et l’état obtenu du tas managé après ces collections, ainsi qu’une arborescence des appels présentant les allocations par méthode et les chargements d’assembly. Pour plus d’informations, consultez Performances.

Propriétés de dépendance et objets

En général, l’accès à une propriété de dépendance d’un n’est DependencyObject pas plus lent que l’accès à une propriété CLR. Bien qu’il y ait une petite surcharge de performances pour définir une valeur de propriété, l’obtention d’une valeur est aussi rapide que l’obtention de la valeur à partir d’une propriété CLR. La légère baisse des performances s’explique par le fait que les propriétés de dépendance prennent en charge des fonctionnalités robustes, comme la liaison de données, l’animation, l’héritage et les styles. Pour plus d’informations, consultez Vue d’ensemble des propriétés de dépendance.

Optimisations de DependencyProperty

Vous devez soigneusement définir les propriétés de dépendance dans votre application. Si vous DependencyProperty affectez uniquement les options de métadonnées de type de rendu, plutôt que d’autres options de métadonnées telles que AffectsMeasure, vous devez la marquer comme telle en substituant ses métadonnées. Pour plus d’informations sur la substitution ou l’obtention de métadonnées de propriété, consultez Métadonnées de propriété de dépendance.

Il peut être plus efficace d’avoir un gestionnaire de changement de propriété qui invalide manuellement les passes de mesure, d’organisation et d’affichage si tous les changements de propriété n’affectent pas réellement la mesure, l’organisation et l’affichage. Par exemple, vous pouvez décider de réafficher un arrière-plan uniquement quand une valeur est supérieure à une limite définie. Dans ce cas, votre gestionnaire de changement de propriété invalide l’affichage uniquement quand la valeur dépasse la limite définie.

Rendre une propriété de dépendance héritable n’est pas gratuit

Par défaut, les propriétés de dépendance inscrites ne sont pas héritables. Toutefois, vous pouvez explicitement rendre une propriété héritable. Bien qu’il s’agisse d’une fonctionnalité utile, la conversion d’une propriété pour qu’elle soit héritable affecte les performances en augmentant la durée d’invalidation de la propriété.

Utiliser RegisterClassHandler avec précaution

Lors de l’appel RegisterClassHandler vous permet d’enregistrer l’état de votre instance, il est important de savoir que le gestionnaire est appelé sur chaque instance, ce qui peut entraîner des problèmes de performances. RegisterClassHandler Utilisez uniquement lorsque votre application nécessite d’enregistrer l’état de votre instance.

Définir la valeur par défaut d’une propriété DependencyProperty pendant l’inscription

Lors de la création d’une DependencyProperty valeur par défaut, définissez la valeur à l’aide des métadonnées par défaut passées en tant que paramètre à la Register méthode du DependencyProperty. Utilisez cette technique au lieu de définir la valeur de propriété dans un constructeur ou sur chaque instance d’un élément.

Définir la valeur PropertyMetadata à l’aide du Registre

Lors de la création d’un DependencyProperty, vous avez la possibilité de définir l’utilisation PropertyMetadata des méthodes ou OverrideMetadata des Register méthodes. Bien que votre objet puisse avoir un constructeur statique à appeler OverrideMetadata, il ne s’agit pas de la solution optimale et aura un impact sur les performances. Pour des performances optimales, définissez le PropertyMetadata pendant l’appel sur Register.

Objets Freezable

Il Freezable s’agit d’un type spécial d’objet qui a deux états : unfrozen et figé. Le fait de figer les objets chaque fois que cela est possible améliore les performances de votre application et réduit son jeu de travail. Pour plus d’informations, consultez Vue d’ensemble des objets Freezable.

Chaque Freezable événement est Changed déclenché chaque fois qu’il change. Toutefois, les notifications de changement détériorent les performances de l’application.

Considérez l’exemple suivant dans lequel chacun Rectangle utilise le même Brush objet :

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

Par défaut, WPF fournit un gestionnaire d’événements pour l’événement de l’objet SolidColorBrush afin d’invalider la propriété de l’objet FillRectangle.Changed Dans ce cas, chaque fois SolidColorBrush qu’il doit déclencher son Changed événement, il est nécessaire d’appeler la fonction de rappel pour chacun Rectangle, l’accumulation de ces appels de fonction de rappel impose une pénalité de performances significative. Par ailleurs, l’ajout et la suppression des gestionnaires à ce stade consomment une grande part des performances, car cette opération oblige l’application à parcourir l’intégralité de la liste. Si votre scénario d’application ne change SolidColorBrushjamais, vous payez le coût de maintenance Changed inutile des gestionnaires d’événements.

Geler un Freezable peut améliorer ses performances, car il n’a plus besoin de dépenser des ressources pour maintenir les notifications de modification. Le tableau ci-dessous montre la taille d’un simple SolidColorBrush lorsque sa IsFrozen propriété est définie truesur , par rapport au moment où elle n’est pas. Cela suppose l’application d’un pinceau à la Fill propriété de dix Rectangle objets.

État Taille
La Reine des neiges SolidColorBrush 212 octets
Non figé SolidColorBrush 972 octets

L'exemple de code suivant illustre ce concept :

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

Les gestionnaires de changement sur des Freezables non figés peuvent conserver les objets actifs

Le délégué qu’un objet passe à l’événement d’un FreezableChanged objet est effectivement une référence à cet objet. Par conséquent, Changed les gestionnaires d’événements peuvent conserver les objets vivants plus longtemps que prévu. Lors de l’exécution de propre d’un objet inscrit pour écouter l’événement d’un FreezableChanged objet, il est essentiel de supprimer ce délégué avant de libérer l’objet.

WPF connecte également des Changed événements en interne. Par exemple, toutes les propriétés de dépendance qui prennent Freezable comme valeur écoutent Changed automatiquement les événements. La Fill propriété, qui prend un Brush, illustre ce concept.

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

Lors de l’affectation de myBrushmyRectangle.Filll’objet , un délégué pointant vers l’objet Rectangle est ajouté à l’événement de Changed l’objetSolidColorBrush. Cela signifie que le code suivant ne rend pas myRect éligible pour l’opération de garbage collection :

myRectangle = null;
myRectangle = Nothing

Dans ce cas myBrush , il est toujours myRectangle en vie et le rappelle lorsqu’il déclenche son Changed événement. Notez que l’affectation myBrush à la Fill propriété d’un nouveau Rectangle va simplement ajouter un autre gestionnaire d’événements à myBrush.

La méthode recommandée pour propre ces types d’objets consiste à supprimer la BrushFill propriété, ce qui supprimera à son tour le Changed gestionnaire d’événements.

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

Virtualisation de l'interface utilisateur

WPF fournit également une variante de l’élément StackPanel qui « virtualise » automatiquement le contenu enfant lié aux données. Dans ce contexte, le mot virtualiser fait référence à une technique par laquelle une partie des objets est généré à partir d’un plus grand nombre d’éléments de données en fonction des éléments visibles à l’écran. La génération d’un grand nombre d’éléments d’interface utilisateur, alors que seul un certain nombre de ces éléments peut figurer à l’écran à un moment donné, entraîne une utilisation intensive de la mémoire et du processeur. VirtualizingStackPanel (via les fonctionnalités fournies par VirtualizingPanel) calcule les éléments visibles et fonctionne avec un ItemContainerGeneratorItemsControl (par exemple ListBox ) ListViewpour créer uniquement des éléments pour les éléments visibles.

Pour optimiser les performances, des objets visuels pour ces éléments sont générés ou maintenus actifs uniquement s’ils sont visibles à l’écran. Quand ils ne sont plus dans la zone visible du contrôle, les objets visuels peuvent être supprimés. Cette virtualisation ne doit pas être confondue avec la virtualisation des données, où les objets de données ne sont pas tous présents dans la collection locale, mais diffusés selon les besoins.

Le tableau ci-dessous montre le temps écoulé d’ajout et de rendu de 5 000 TextBlock éléments à un StackPanel et un VirtualizingStackPanel. Dans ce scénario, les mesures représentent le temps entre l’attachement d’une chaîne de texte à la ItemsSource propriété d’un ItemsControl objet à l’heure où les éléments du panneau affichent la chaîne de texte.

Panneau hôte Temps d’affichage (ms)
StackPanel 3210
VirtualizingStackPanel 46

Voir aussi