Question

J'ai créé un "comportement attaché" dans mon application WPF qui me permet de gérer la pression sur la touche Entrée et de passer au contrôle suivant.Je l'appelle EnterKeyTraversal.IsEnabled, et vous pouvez voir le code sur mon blog ici.

Ma principale préoccupation maintenant est que je peux avoir une fuite de mémoire, puisque je gère l'événement PreviewKeyDown sur UIElements et que je ne "décroche" jamais explicitement l'événement.

Quelle est la meilleure approche pour éviter cette fuite (s'il y en a une) ?Dois-je conserver une liste des éléments que je gère et décrocher l'événement PreviewKeyDown dans l'événement Application.Exit ?Quelqu'un a-t-il réussi avec les comportements attachés dans ses propres applications WPF et a-t-il proposé une solution élégante de gestion de la mémoire ?

Était-ce utile?

La solution

Je ne suis pas d'accord DannySmurf

Certains objets de présentation WPF peuvent encombrer votre mémoire et rendre votre application très lente lorsqu'ils ne sont pas récupérés.Je trouve donc que le choix des mots est correct, vous perdez de la mémoire vers des objets que vous n'utilisez plus.Vous vous attendez à ce que les éléments soient récupérés, mais ils ne le sont pas, car il y a une référence quelque part (dans ce cas, dans le gestionnaire d'événements).

Maintenant pour une vraie réponse :)

je te conseille de lire ceci Article sur les performances WPF sur MSDN

Ne pas supprimer les gestionnaires d'événements sur les objets peut garder les objets en vie

Le délégué qu'un objet transmet à son événement est effectivement une référence à cet objet.Par conséquent, les gestionnaires d'événements peuvent garder les objets en vie plus longtemps que prévu.Lorsque vous effectuez un nettoyage d'un objet qui s'est inscrit pour écouter l'événement d'un objet, il est essentiel de supprimer ce délégué avant de libérer l'objet.Garder les objets inutiles en vie augmente l'utilisation de la mémoire de l'application.Cela est particulièrement vrai lorsque l'objet est la racine d'un arbre logique ou d'un arbre visuel.

Ils vous conseillent de vous renseigner sur Modèle d'événement faible

Une autre solution serait de supprimer les gestionnaires d'événements lorsque vous avez terminé avec un objet.Mais je sais qu'avec les propriétés attachées, ce point n'est peut-être pas toujours clair.

J'espère que cela t'aides!

Autres conseils

Débat philosophique mis à part, en regardant le billet de blog du PO, je ne vois aucune fuite ici :

ue.PreviewKeyDown += ue_PreviewKeyDown;

Une dure référence à ue_PreviewKeyDown est stocké dans ue.PreviewKeyDown.

ue_PreviewKeyDown est un STATIC méthode et ne peut pas être GCed.

Aucune référence concrète à ue est stocké, donc rien ne l'empêche d'être GCed.

Donc...Où est la fuite ?

Oui, je sais qu’autrefois, les fuites de mémoire étaient un sujet totalement différent.Mais avec le code managé, une nouvelle signification du terme fuite de mémoire pourrait être plus appropriée...

Microsoft reconnaît même qu'il s'agit d'une fuite de mémoire :

Pourquoi implémenter le modèle WeakEvent ?

L'écoute des événements peut entraîner des fuites de mémoire.La technique typique pour écouter un événement est d'utiliser la syntaxe spécifique à la langue qui attache un gestionnaire à un événement sur une source.Par exemple, en C #, cette syntaxe est:Source.SomeEvent + = new SomeeventHandler (MyEventHandler).

Cette technique crée une forte référence de la source de l'événement à l'auditeur de l'événement.Habituellement, la fixation d'un gestionnaire d'événements pour un auditeur fait que l'auditeur a une durée de vie d'objet qui a influencé la durée de vie de l'objet pour la source (à moins que le gestionnaire d'événements ne soit explicitement supprimé).Mais dans certaines circonstances, vous voudrez peut-être que la durée de vie de l'objet de l'auditeur ne soit contrôlée que par d'autres facteurs, tels qu'il appartient actuellement à l'arbre visuel de l'application, et non par la durée de vie de la source.Chaque fois que la durée de vie de l'objet source s'étend au-delà de la durée de vie de l'objet de l'auditeur, le modèle d'événement normal conduit à une fuite de mémoire:L'auditeur est maintenu en vie plus longtemps que prévu.

Nous utilisons WPF pour une application client avec de grandes ToolWindows qui peuvent être glissées-déposées, tous les trucs astucieux et tous compatibles avec dans un XBAP.Mais nous avons eu le même problème avec certaines ToolWindows qui n'étaient pas récupérées.Cela était dû au fait qu'il dépendait toujours des auditeurs d'événements.Cela ne posera peut-être plus de problème lorsque vous fermez votre fenêtre et fermez votre application.Mais si vous créez de très grandes ToolWindows avec beaucoup de commandes, et que toutes ces commandes sont réévaluées encore et encore, et que les gens doivent utiliser votre application toute la journée.Je peux vous dire..cela obstrue vraiment votre mémoire et le temps de réponse de votre application.

De plus, je trouve beaucoup plus facile d'expliquer à mon manager que nous avons une fuite de mémoire, que de lui expliquer que certains objets ne sont pas récupérés en raison de certains événements qui nécessitent un nettoyage ;)

@Nick Ouais, le problème avec les comportements attachés est que, par définition, ils ne se trouvent pas dans le même objet que les éléments dont vous gérez les événements.

Je pense que la réponse réside dans l'utilisation de WeakReference, mais je n'ai vu aucun exemple de code simple pour me l'expliquer.:)

Avez-vous pensé à implémenter le « modèle d'événements faibles » au lieu d'événements réguliers ?

  1. Modèle d'événement faible dans WPF
  2. Modèles d'événements faibles (MSDN)

Pour expliquer mon commentaire sur l'article de John Fenton, voici ma réponse.Voyons l'exemple suivant :

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();

        a.Clicked += b.HandleClicked;
        //a.Clicked += B.StaticHandleClicked;
        //A.StaticClicked += b.HandleClicked;

        var weakA = new WeakReference(a);
        var weakB = new WeakReference(b);

        a = null;
        //b = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.WriteLine("a is alive: " + weakA.IsAlive);
        Console.WriteLine("b is alive: " + weakB.IsAlive);
        Console.ReadKey();
    }


}

class A
{
    public event EventHandler Clicked;
    public static event EventHandler StaticClicked;
}

class B
{
    public void HandleClicked(object sender, EventArgs e)
    {
    }

    public static void StaticHandleClicked(object sender, EventArgs e)
    {
    }
}

Si tu as

a.Clicked += b.HandleClicked;

et définissez uniquement b sur null, les deux références faibleA et faibleB restent en vie !Si vous définissez uniquement a sur null, b reste vivant mais pas a (ce qui prouve que John Fenton a tort en déclarant qu'une référence matérielle est stockée dans le fournisseur d'événements - dans ce cas, a).

Cela m'amène à la FAUSSE conclusion selon laquelle

a.Clicked += B.StaticHandleClicked;

entraînerait une fuite car je pensais que l'instance de a serait conservée par le gestionnaire statique.Ce n'est pas le cas (testez mon programme).Dans le cas d'un gestionnaire d'événements ou d'événements statiques, c'est l'inverse.Si tu écris

A.StaticClicked += b.HandleClicked;

une référence sera conservée à b.

Assurez-vous que les éléments faisant référence à des événements se trouvent dans l'objet auquel ils font référence, comme les zones de texte dans le contrôle de formulaire.Ou si cela ne peut être évité.Créez un événement statique sur une classe d'assistance globale, puis surveillez la classe d'assistance globale pour les événements.Si ces deux étapes ne peuvent pas être effectuées, essayez d'utiliser une WeakReference, elles sont généralement parfaites pour ces situations, mais elles entraînent une surcharge.

Je viens de lire votre article de blog et je pense que vous avez reçu des conseils trompeurs, Matt.S'il y a un réel mémoire fuir ici, alors c'est un bug dans le .NET Framework, et pas quelque chose que vous pouvez nécessairement corriger dans votre code.

Ce dont je pense que vous (et l'affiche sur votre blog) parlez ici n'est pas réellement une fuite, mais plutôt une consommation continue de mémoire.Ce n'est pas la même chose.Pour être clair, une fuite de mémoire est une mémoire réservée par un programme, puis abandonnée (c'est-à-dire qu'un pointeur reste suspendu) et qui ne peut ensuite pas être libérée.La mémoire étant gérée dans .NET, cela est théoriquement impossible.Il est cependant possible pour un programme de réserver une quantité toujours croissante de mémoire sans permettre aux références à celle-ci de sortir de sa portée (et de devenir éligibles au garbage collection) ;cependant, cette mémoire n'est pas divulguée.Le GC le renverra au système une fois votre programme terminé.

Donc.Pour répondre à votre question, je ne pense pas que vous ayez réellement de problème ici.Vous n'avez certainement pas de fuite de mémoire, et d'après votre code, je ne pense pas que vous ayez à vous inquiéter non plus en ce qui concerne la consommation de mémoire.Tant que vous vous assurez que vous n'attribuez pas ce gestionnaire d'événements à plusieurs reprises sans jamais le désaffecter (c'est-à-dire que vous ne le définissez qu'une seule fois ou que vous le supprimez exactement une fois pour chaque fois que vous l'attribuez), ce qui vous semblez le faire, votre code devrait fonctionner correctement.

Il semble que ce soit le conseil que l'auteur de votre blog essayait de vous donner, mais il a utilisé cette "fuite" de travail alarmante, qui est un mot effrayant, mais dont de nombreux programmeurs ont oublié le véritable sens dans le monde géré ;cela ne s'applique pas ici.

@Arcturus :

...obstruez votre mémoire et rendez votre application vraiment ralentie lorsqu'elles ne sont pas collectées aux ordures.

C’est d’une évidence aveuglante et je ne suis pas en désaccord.Cependant:

... vous fuisez de la mémoire à l'objet que vous n'utilisez plus ...Parce qu'il y a une référence à eux.

"la mémoire est allouée à un programme, et ce programme perd par la suite la possibilité d'y accéder en raison de défauts de logique du programme" (Wikipédia, "Fuite de mémoire")

S'il existe une référence active à un objet à laquelle votre programme peut accéder, alors par définition il n'y a pas de fuite de mémoire.Une fuite signifie que l'objet n'est plus accessible (à vous ou à l'OS/Framework), et ne sera pas libéré pour la durée de vie de la session en cours du système d'exploitation.Ce n'est pas le cas ici.

(Désolé d'être un nazi sémantique...je suis peut-être un peu old school, mais la fuite a une signification très précise.De nos jours, les gens ont tendance à utiliser « fuite de mémoire » pour désigner tout ce qui consomme 2 Ko de mémoire de plus qu'ils ne le souhaitent...)

Mais bien sûr, si vous ne libérez pas de gestionnaire d'événements, l'objet auquel il est attaché ne sera pas libéré tant que la mémoire de votre processus n'aura pas été récupérée par le garbage collector à l'arrêt.Mais ce comportement est tout à fait attendu, contrairement à ce que vous semblez laisser entendre.Si vous vous attendez à ce qu'un objet soit récupéré, vous devez supprimer tout ce qui peut maintenir la référence en vie, y compris les gestionnaires d'événements.

Vrai vrai,

Tu as raison, bien sûr..Mais une toute nouvelle génération de programmeurs est en train de naître dans ce monde qui ne touchera jamais au code non géré, et je crois que les définitions du langage se réinventeront encore et encore.Les fuites de mémoire dans WPF sont différentes de celles, par exemple, de C/Cpp.

Ou bien sûr, à mes managers, j'ai parlé de fuite de mémoire.à mes collègues, j'en ai parlé comme d'un problème de performance !

En ce qui concerne le problème de Matt, il pourrait s'agir d'un problème de performances que vous devrez peut-être résoudre.Si vous n'utilisez que quelques écrans et que vous créez des contrôles d'écran uniques, vous ne verrez peut-être pas ce problème du tout ;).

Eh bien, je peux certainement comprendre (le morceau du manager) et sympathiser avec cela.

Mais quel que soit le nom que Microsoft lui donne, je ne pense pas qu'une « nouvelle » définition soit appropriée.C'est compliqué, car nous ne vivons pas dans un monde géré à 100 % (même si Microsoft aime prétendre que c'est le cas, Microsoft lui-même ne vit pas dans un tel monde).Lorsque vous parlez de fuite de mémoire, vous pouvez vouloir dire qu'un programme consomme trop de mémoire (c'est la définition de l'utilisateur), ou qu'une référence gérée ne sera pas libérée jusqu'à la sortie (comme ici), ou qu'une référence non gérée n'est pas correctement nettoyée. (ce serait une véritable fuite de mémoire), ou que le code non managé appelé à partir du code managé perd de la mémoire (une autre vraie fuite).

Dans ce cas, ce que signifie « fuite de mémoire » est évident, même si nous sommes imprécis.Mais cela devient terriblement fastidieux de parler à certaines personnes, qui qualifient toute surconsommation ou tout échec de collecte de fuite de mémoire ;et c'est frustrant quand ces gens sont des programmeurs, censés savoir mieux.Il est important que les termes techniques aient un sens sans ambiguïté, je pense.Le débogage est tellement plus facile quand ils le font.

De toute façon.Je ne veux pas transformer cela en une discussion fantaisiste sur la langue.Je dis juste...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top