Partager via


Optimisation des performances : comportement d'objets

Le fait de bien comprendre le comportement intrinsèque des objets WPF vous aidera à effectuer les bons compromis entre fonctionnalités et performances.

Cette rubrique comprend les sections suivantes.

  • Le fait de ne pas supprimer les gestionnaires d'événements sur des objets peut conserver des objets actifs
  • Propriétés de dépendance et objets
  • Objets Freezable
  • Virtualisation de l'interface utilisateur
  • Rubriques connexes

Le fait de ne pas supprimer les gestionnaires d'événements sur des objets peut conserver des objets actifs

Le délégué qu'un objet passe à son événement est effectivement une référence à cet objet. Par conséquent, les gestionnaires d'événements peuvent conserver des objets actifs plus longtemps que prévu. Lorsque vous effectuez un nettoyage d'un objet qui s'est inscrit pour se mettre à l'écoute de l'événement d'un objet, il est essentiel de supprimer ce délégué avant de libérer l'objet. Le fait de conserver des objets inutiles en activité augmente l'utilisation de la mémoire de l'application. Ceci est particulièrement vrai lorsque l'objet est la racine d'une arborescence logique ou d'une arborescence visuelle.

WPF introduit un modèle d'écouteur d'événements faible pour les événements qui peut être utile 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, vous trouverez peut-être ce modèle pratique. Pour plus d'informations, consultez Modèles d'événement faible.

Il existe plusieurs outils, par exemple le profileur CLR et la visionneuse de jeu de travail (Working Set Viewer), qui peuvent fournir des informations sur l'utilisation de la mémoire d'un processus spécifié. Le profileur CLR inclut un certain nombre de vues très utiles concernant le 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 en résultat du tas managé après ces collections, ainsi qu'une arborescence d'appels illustrant les allocations par méthode et les chargements d'assemblys. Pour plus d'informations, consultez .NET Framework Developer Center.

Propriétés de dépendance et objets

En général, l'accès à une propriété de dépendance d'un DependencyObject n'est pas plus lent que celui d'une propriété CLR. S'il y a une baisse de performances liée à la définition d'une valeur de propriété, l'obtention d'une valeur est aussi rapide que l'obtention de la valeur d'une propriété CLR. En compensation de la légère baisse des performances, les propriétés de dépendance prennent en charge de puissantes fonctionnalités, comme la liaison des 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 définir les propriétés de dépendance dans votre application très attentivement. Si votre DependencyProperty affecte uniquement des options de métadonnées de type rendu, et non pas les autres options de métadonnées telles que AffectsMeasure, vous devez l'indiquer en substituant ses métadonnées. Pour plus d'informations sur la substitution ou l'obtention de métadonnées de propriétés, consultez Métadonnées de propriété de dépendance.

Il peut être plus efficace d'avoir un gestionnaire de changement de propriétés qui invalide les passes de mesure, d'organisation et de rendu manuellement, sinon tous les changements de propriétés affecteront en fait les options de mesure, d'organisation et de rendu. 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 changements de propriétés risque d'invalider uniquement le rendu quand la valeur dépasse la limite définie.

Rendre une DependencyProperty héritable n'est pas gratuit

Par défaut, les propriétés de dépendance sont non héritables. Cependant, vous pouvez rendre explicitement héritable toute propriété. S'il s'agit d'une fonction utile, la conversion d'une propriété pour la rendre héritable a un impact sur les performances car cela augmente la durée d'invalidation de la propriété.

Utiliser attentivement RegisterClassHandler

Si le fait d'appeler 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 générer des problèmes de performance. Utilisez RegisterClassHandler uniquement quand votre application exige l'enregistrement de votre état d'instance.

Définir la valeur par défaut d'une DependencyProperty pendant l'enregistrement

Lorsque vous créez une DependencyProperty qui nécessite une valeur par défaut, définissez la valeur à l'aide des métadonnées par défaut passées en tant que paramètres à la méthode Register de la DependencyProperty. Utilisez cette technique au lieu de définir la valeur de la propriété dans un constructeur ou sur chaque instance d'un élément.

Définir la valeur de PropertyMetadata à l'aide du Registre

Lorsque vous créez une DependencyProperty, vous avez la possibilité de définir la PropertyMetadata en utilisant les méthodes Register ou OverrideMetadata. Bien que votre objet puisse avoir un constructeur statique pour appeler OverrideMetadata, ceci n'est pas la solution optimale et aura un impact sur les performances. Pour optimiser les performances, définissez PropertyMetadata pendant l'appel à Register.

Objets Freezable

Un Freezable est un type d'objet spécial qui possède deux états : figé et non figé. Le fait de figer les objets chaque fois que 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 possède un événement Changed qui est généré à chaque changement. Cependant, les notifications de changement sont coûteuses en termes de performances de l'application.

Examinez l'exemple suivant dans lequel chaque Rectangle utilise le même objet 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;

Par défaut, WPF fournit un gestionnaire d'événements pour l'événement Changed de l'objet SolidColorBrush afin d'invalider la propriété Fill de l'objet Rectangle. Dans ce cas, chaque fois que le SolidColorBrush doit déclencher son événement Changed, il doit appeler la fonction de rappel pour chaque Rectangle— l'accumulation de ces appels de fonctions de rappel impose une sensible baisse de performances. En plus, il est bon en terme de performances d'ajouter et de supprimer des gestionnaires à ce stade dans la mesure où l'application aurait à parcourir toute la liste pour le faire. Si votre scénario d'application ne change jamais SolidColorBrush, vous paierez le coût du maintien de gestionnaires d'événements non nécessaires Changed.

Le fait de figer un objet Freezable peut améliorer sa performance, car il n'a plus besoin d'étendre des ressources pour maintenir des notifications de changement. Le tableau ci-dessous montre la taille d'un simple SolidColorBrush quand sa propriété IsFrozen a la valeur true, par rapport à quand elle ne l'a pas. Cela suppose l'application d'un pinceau à la propriété Fill de dix objets Rectangle.

État

Taille

Figé SolidColorBrush

212 octets

Non figé SolidColorBrush

972 octets

L'exemple de code suivant illustre ce concept :

            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;
}

Les gestionnaires modifiés sur des Freezables non figés peuvent conserver des objets actifs

Le délégué qu'un objet passe à un événement Changed d'un objet Freezable est effectivement une référence à cet objet. Par conséquent, les gestionnaires d'événements Changed peuvent conserver des objets actifs plus longtemps que prévu. Lorsque vous effectuez un nettoyage d'un objet qui s'est inscrit pour se mettre à l'écoute de l'événement Freezable d'un objet Changed, il est essentiel de supprimer ce délégué avant de libérer l'objet.

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

            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;

Lors de l'affectation de myBrush à myRectangle.Fill, un délégué pointant en arrière vers l'objet Rectangle sera ajouté à l'événement Changed de l'objet SolidColorBrush. Cela signifie que le fait de suivre le code ne qualifie pas en fait myRect pour une opération de garbage collection :

            myRectangle = Nothing
myRectangle = null;

Dans ce cas myBrush maintien myRectangle actif et le rappellera lorsqu'il déclenchera son événement Changed. Le fait d'affecter myBrush à la propriété Fill d'un nouveau Rectangle ne fera qu'ajouter un gestionnaire d'événements à myBrush.

La méthode recommandée pour nettoyer ces types d'objets est de supprimer le Brush de la propriété Fill, qui à son tour supprimera le gestionnaire d'événements Changed.

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

Virtualisation de l'interface utilisateur

WPF fournit également une variante de l'élément StackPanel qui « virtualise » automatiquement du contenu enfant lié aux données. Dans ce contexte, le mot « virtualiser » fait référence à une technique par laquelle un sous-ensemble d'objets est généré à partir d'un plus grand nombre d'éléments de données, sur la base des éléments qui sont visibles à l'écran. Générer un grand nombre d'éléments de l'interface utilisateur, alors que seuls un certain nombre d'entre eux peuvent être visibles sur l'écran à un moment donné, provoque une utilisation intensive de la mémoire et du processeur. VirtualizingStackPanel (via la fonctionnalité fournie par VirtualizingPanel) calcule les éléments visibles et utilise ItemContainerGenerator à partir d'un ItemsControl (tel que ListBox ou ListView) pour créer uniquement des éléments pour les éléments visibles.

En guise d'optimisation de performances, les objets visuels pour ces éléments sont uniquement générés ou maintenus actifs s'ils sont visibles à l'écran. Quand ils ne sont plus dans la zone visualisable du contrôle, les objets visuels peuvent être supprimés. Cette situation ne doit pas confondue avec la virtualisation des données, où les objets de données ne sont pas du tout présents dans la collection locale – mais plutôt transmis selon les besoins.

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

Panneau hôte

Restituer l'heure (ms)

StackPanel

3210

VirtualizingStackPanel

46

Voir aussi

Concepts

Optimisation des performances des applications WPF

Planification des performances des applications

Optimisation des performances : tirer parti du matériel

Optimisation des performances : disposition et conception

Optimisation des performances : graphiques 2D et acquisition d'images

Optimisation des performances : ressources d'application

Optimisation des performances : texte

Optimisation des performances : liaison de données

Optimisation des performances : autres recommandations