2009-12-07 7 views
13

J'ai une application WPF basée sur PRISM qui utilise le modèle MVVM.Quelle est la meilleure façon d'éviter les fuites de mémoire dans l'application WPF PRISM/MVVM?

J'ai remarqué qu'à l'occasion, les vues, les vues et tout ce qui leur est associé restent longtemps après leur durée de vie. Une fuite impliquait l'abonnement à CollectionChanged sur une collection appartenant à un service injecté, un autre impliqué n'appelant pas la méthode Stop sur un DispatcherTimer, et un autre nécessitant qu'une collection soit effacée de ses éléments. Je pense que l'utilisation d'un CompositePresentationEvent est probablement préférable à l'abonnement à CollectionChanged, mais dans les autres scénarios, je penche pour l'implémentation d'IDisposable et les vues appellent la méthode Dispose sur les modèles de vue. Mais alors quelque chose doit indiquer à la vue quand appeler Dispose sur le modèle de vue, qui devient encore moins attrayant quand la complexité des vues augmente, et ils commencent à inclure des vues enfant.

Selon vous, quelle est la meilleure approche pour gérer les modèles de vue, pour s'assurer qu'ils ne fuient pas la mémoire?

Merci à l'avance

Ian

Répondre

14

je peux vous dire que je l'ai connu 100% de la douleur que vous avez vécu. Nous sommes des frères de fuite de mémoire, je pense.

Malheureusement, la seule chose que j'ai pensé faire ici est quelque chose de très similaire à ce que vous pensez.

Ce que nous avons fait est de créer une propriété attachée qu'une vue peut appliquer à lui-même pour lier un gestionnaire à la ViewModel:

<UserControl ... 
      common:LifecycleManagement.CloseHandler="{Binding CloseAction}"> 
... 
</UserControl> 

Alors notre ViewModel a juste une méthode sur elle de type d'action:

public MyVM : ViewModel 
{ 
    public Action CloseAction 
    { 
      get { return CloseActionInternal; } 
    } 

    private void CloseActionInternal() 
    { 
      //TODO: stop timers, cleanup, etc; 
    } 
} 

Quand mes feux à proximité de la méthode (nous avons quelques façons de le faire ... il est une interface utilisateur TabControl avec « X » sur les réglettes, ce genre de chose), je vérifie simplement si ce point de vue s'est enregistré avec AttachedProperty. Si c'est le cas, j'appelle la méthode référencée ici.

C'est une façon plutôt détournée de vérifier simplement si le DataContext d'une vue est une IDposable, mais il se sentait mieux à ce moment-là. Le problème avec la vérification du DataContext est que vous pourriez avoir des modèles sous vue qui ont aussi besoin de ce contrôle. Vous devez soit vous assurer que votre viewmodels enchaîne cet appel en disposer ou vérifier toutes les vues dans le graphique et voir si leurs datacontexts sont IDisposable (ugh). J'ai l'impression qu'il manque quelque chose ici. Il existe quelques autres cadres qui tentent d'atténuer ce scénario par d'autres moyens. Vous pourriez jeter un oeil à Caliburn. Il dispose d'un système permettant de gérer cela lorsqu'un ViewModel est conscient de tous les modèles de sous-vue, ce qui lui permet d'enchaîner automatiquement les choses. En particulier, il existe une interface appelée ISupportCustomShutdown (je pense que c'est ce que l'on appelle) qui aide à atténuer ce problème. La meilleure chose que j'ai faite, cependant, est de m'assurer et d'utiliser de bons outils de fuite de mémoire comme Redgate Memory Profiler qui vous aident à visualiser le graphe d'objets et à trouver l'objet racine. Si vous étiez en mesure d'identifier ce problème DispatchTimer, j'imagine que vous le faites déjà.

Edit: Je oublié une chose importante. Il existe une fuite de mémoire potentielle provoquée par l'un des gestionnaires d'événements dans DelegateCommand. Voici un fil à propos de Codeplex qui l'explique. http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065

La dernière version du prisme (v2.1) a fixé ce. (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en).

+0

LOL, il est un trou profond, je me suis en train de creuser ici;) Je suis curieux de ce que votre comportement attaché crochets à le UserControl, j'ai cherché un moyen de détecter le moment où la vue est terminée. Déchargé semble être le choix évident, mais ce n'est pas très utile car il se déclenchera dès que la vue deviendra inactive –

+0

Ah c'est sur mesure.Chaque fois qu'une vue doit se fermer, elle appelle une méthode sur un ensemble de commandes d'application que nous avons. CloseView (vue d'objet). Je vérifie auprès du magasin derrière la propriété jointe pour voir si cette vue ou l'une de ses vues enfant sont enregistrées pour avoir des gestionnaires proches. Si oui, j'appelle la méthode qu'ils ont enregistrée. La seule magie ici est l'utilisation des classes LogicalTreeHelper et VisualTreeHelper pour localiser les vues enfants. http://msdn.microsoft.com/en-us/library/system.windows.media.visualtreehelper.aspx –

+0

Nous avons essayé déchargé aussi, BTW. Dans certains cas, c'était correct ... nous arrêtons les minuteurs qui consistaient essentiellement à rafraîchir les données de toute façon et il n'y avait aucune raison de les rafraîchir si la vue n'était pas visible (comme si c'était sur des onglets ou quelque chose), mais parfois J'ai juste besoin d'une minuterie pour ne jamais m'arrêter à moins que la vue ne soit tuée et cette chose fermée explicite était la meilleure que nous pouvions trouver. –

2

Mes résultats jusqu'à présent ...

En plus de PRISM, l'unité, WPF et MVVM nous utilisons également Entity Framework et la grille de données Xceed. Le profilage de la mémoire a été fait en utilisant dotTrace.

Je fini par mettre en œuvre IDisposable sur une classe de base pour mes modèles de vue avec la méthode Dispose (bool) permettant d'être déclaré sous des classes virtuelles la possibilité de nettoyer aussi bien. Comme chaque modèle de vue de notre application obtient un conteneur enfant à partir de Unity, nous l'éliminons également, dans notre cas, cela garantit que le ObjectContext de EF est hors de portée. C'était notre principale source de fuites de mémoire.

Le modèle de vue est disposé selon une méthode explicite CloseView (UserControl) sur une classe de contrôleur de base. Il recherche un IDisposable sur le DataContext de la vue et appelle Dispose sur celui-ci.

La grille de données Xceed semble provoquer une part équitable des fuites, en particulier dans les vues longues. Toute vue qui actualise ItemSource de la grille de données en assignant une nouvelle collection doit appeler Clear() sur la collection existante avant d'affecter la nouvelle.

Soyez prudent avec Entity Framework et évitez les contextes d'objets en cours d'exécution. C'est très impitoyable quand il s'agit de grandes collections, même si vous avez supprimé la collection si le suivi est activé, il contiendra une référence à chaque élément de la collection, même si vous ne les accrochez plus.

Si vous n'avez pas besoin de mettre à jour l'entité, récupérez-la avec MergeOption.NoTracking, en particulier dans les vues de longue durée qui se lient aux collections.

Évitez les vues avec une longue durée de vie, ne les gardez pas dans une région lorsqu'elles ne sont pas visibles, cela vous causera du chagrin surtout si elles actualisent leurs données à intervalles réguliers lorsqu'elles sont visibles.

Lorsque vous utilisez CellContentTemplates sur la colonne Xceed ne pas utiliser les ressources dynamiques comme la ressource tiendra une référence à la cellule, qui à son tour garder toute la vue en vie. Lorsque vous utilisez CellEditor sur la colonne Xceed et que la ressource est stockée dans un dictionnaire de ressources externe, ajoutez x: Shared = "False" à la ressource contenant CellEditor. Une fois de plus, la ressource contiendra une référence à la cellule, en utilisant x : Partagé = "Faux" vous assure d'obtenir une nouvelle copie à chaque fois, l'ancienne étant supprimée correctement. Faites attention lorsque vous liez DelegateCommand à des éléments de la grille de données Exceed. Si vous avez un cas tel qu'un bouton de suppression sur la ligne qui se lie à une commande, veillez à effacer la collection contenant ItemsSource avant de fermer la vue. . Si vous actualisez la collection, vous devez également réinitialiser la commande et la commande contiendra une référence à chaque ligne.

+0

Cela soulève un bon point que je devrais mentionner. La dernière version de Prism a une fuite de mémoire potentielle dans DelegateCommand. Il aurait dû utiliser des références faibles pour l'un des gestionnaires d'événements, mais ce n'était pas le cas. J'ai fini par forcer le code et le corriger moi-même, mais depuis, ils se sont fixés dans la source qui est vérifiée dans le dépôt sur codeplex. Tirez vers le bas la dernière source et compilez et vous devriez éviter celui-ci aussi. –

Questions connexes