2010-06-15 8 views
8

Ich habe gerade Desktop-Apps fertig geschrieben in WPF und C# mit MVVM-Muster. In dieser App habe ich die Delegate Command-Implementierung verwendet, um die ICommands-Eigenschaften zu umbrechen, die in meinem ModelView verfügbar sind. Das Problem ist, dass diese DelegateCommands verhindern, dass ModelView und View nach dem Schließen der Ansicht als Garbage Collected erfasst werden. Es bleibt also solange larking, bis ich die gesamte Anwendung beendet habe. Ich profiliere die Anwendung Ich finde, dass es alles über delegatecommand ist, der das modelview in Gedächtnis behält. Wie könnte ich diese Situation vermeiden und ist dies in der Natur der MVVM-Muster, oder es ist über meine Implantation des Musters ?. Vielen Dank.Speicherverlust in der WPF-App aufgrund von DelegateCommand

Edit: dies ist klein, aber komplette Teil, wie ich MVVM Muster

Erste Umsetzung: CommandDelegte Klasse

class DelegateCommand:ICommand 
{ 
    private Action<object> execute; 
    private Predicate<object> canExcute; 
    public DelegateCommand(Action<object> execute, Predicate<object> canExecute) 
    { 
     if (execute == null) 
     { 
      throw new ArgumentNullException("execute"); 
     } 
     this.execute = execute; 
     this.canExcute = canExecute; 
    } 
    public bool CanExecute(object parameter) 
    { 
     if (this.canExcute != null) 
     { 
      return canExcute(parameter); 
     } 
     return true; 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 


    public void Execute(object parameter) 
    { 
     this.execute(parameter); 
    } 
} 

Zweitens: Modelview Klasse

public class ViewModel:DependencyObject, INotifyPropertyChanged 
{ 
    private DelegateCommand printCommand; 

    public ICommand PrintCommand 
    { 
     get 
     { 
      if (printCommand == null) 
      { 
       printCommand = new DelegateCommand(Print, CanExecutePrint); 
      } 
      return printCommand; 
     } 
    } 
    void Print(object obj) 
    { 
     Console.WriteLine("Print Command"); 

    } 
    bool CanExecutePrint(object obj) 
    { 
     return true; 
    } 


    public event PropertyChangedEventHandler PropertyChanged; 
    private void OnProeprtyChanged(string propertyName) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

Drittens: Fenster Code hinter

public MainWindow() 
    { 
     InitializeComponent(); 
     base.DataContext = new ViewModel(); 
    } 

Forth: Meine XAML

<Window x:Class="WpfApplication1.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="350" Width="525"> 
<Window.InputBindings> 
    <KeyBinding Key="P" Modifiers="Control" Command="{Binding Path=PrintCommand}"/> 
</Window.InputBindings> 
<StackPanel> 
    <Button Content="Print - Ctrl+P" Width="75" Height="75" Command="{Binding Path=PrintCommand}"/> 
</StackPanel> 

+0

Sie sollten wirklich 'Func ' anstelle von 'Prädikat ' ... http://stackoverflow.com/questions/665494/why-funct-bool-instead-of-predicatet –

Antwort

8

In Ihrem Fall, was enthält einen Verweis auf was?

  1. DelegateCommand enthält einen Verweis auf ViewModel - seine executecanExecute und Eigenschaften enthalten Verweise auf ein Verfahren der ViewModel Instanz.

  2. ViewModel enthält einen Verweis auf DelegateCommand - seine PrintCommand Eigenschaft.

  3. Die Ansicht enthält eine beliebige Anzahl von Verweisen auf die ViewModel.

  4. Die CommandManager enthält einen Verweis auf DelegateCommand in seinem RequerySuggested Ereignis.

Das letzte Referenz ist ein Sonderfall: CommandManager verwendet eine WeakReference in seiner RequerySuggested Ereignis, so dass trotz der Tatsache, dass DelegateCommand Register für dieses Ereignis, es noch Garbage Collection sein kann.

Angesichts all dies sollten Sie kein Problem haben. Wenn die Ansicht entsorgt wird, sollte weder die ViewModel noch die DelegateCommand erreichbar sein.

Sie sagen, Sie haben die Anwendung profiliert und DelegateCommand hält einen Verweis auf ViewModel. Es scheint mir, dass die logische nächste Frage sein sollte: Was enthält einen Verweis auf DelegateCommand? Es sollte nicht CommandManager sein. Haben Sie etwas anderes in Ihrer Anwendung, das auf Ihre Befehle verweist?

+0

@Robert verwenden Rossney: Sehr guter Anfangspunkt, aber wie sieht es mit der Bindung von Eigenschaften und Befehlen aus ViewModel aus, die das PropertyChanged-Ereignis abonnieren wird, um benachrichtigt zu werden, werden diese von WPF Framework verwaltet? Gibt es Bedenken wegen dieser verbindlichen Operationen? Danke .. –

+0

Um diese Frage definitiv zu beantworten, müsste ich mit Reflector herumstochern, aber ich bin ziemlich sicher, dass die Bindung nur Verweise auf ihre Quelle und ihr Ziel enthält, und nichts anderes hat einen Verweis auf die Bindung. Wenn also der Garbage Collector das Ansichts- oder Ansichtsmodell nicht von der Wurzel aus finden kann, hat die Tatsache, dass sie beide auf eine Bindung verweisen und darauf Bezug nehmen, keinen Einfluss darauf, ob sie Garbage-Collected erhalten. Aber du verwendest einen Profiler: Erzählst du dir, was einen Verweis auf 'DelegateCommand' hat? –

+0

Ja, es sagt mir, dass DelegateCommand eine Referenz in ModelView hat und als WeakReference markiert ist, aber sie werden nie gesammelt. Ich weiß, dass ich sie dort finde, weil ich Profil für eine Weile laufen lasse und einen neuen Snapshot mache.Implementieren von IDisposible und Festlegen aller Befehle auf null in Dispose führt zu nichts. Continue ... –

1

Nachdem ich diesen Beitrag gelesen hatte, bin ich auf eine Webseite gestoßen, die einige Informationen enthielt. Es ist eine Seite auf CodePlex genannt Memory Leak caused by DelegateCommand.CanExecuteChanged Event.

Eingetragen von: huetter
Aktualisiert durch: dschenkelman

Wenn meine Anwendung Profilierungs Ich bemerkte, dass viele Eventhandler hatte noch nie von DelegateCommand der CanExecuteChanged-Ereignis abgemeldet. Also diese EventHandler waren nie Müll-Sammler, die einen schweren Speicherverlust verursacht.

Da die Registrierung von CanExecuteChanged-EventHandles außerhalb des Anwendungscodebereichs erfolgt ist, hatte ich erwartet, dass sie automatisch ebenfalls von abgemeldet wird. An dieser Stelle dachte ich, dies könnte auch ein ThirdParty WPF Steuerelement Problem sein, aber weiter graben ich las ein Blog Post, dass "WPF erwartet das ICommand.CanExecuteChanged-Event WeakReferences für EventHandlers anwenden". Ich habe einen Blick in RoutedCommand geworfen, und bemerkte, dass es auch WeakReferences verwendet.

Ich passte DelegateCommand an, um eine ähnliche Implementierung wie CanExecuteChanged-Event von RoutedCommand zu verwenden, und das Speicherleck war gegangen. Das Gleiche gilt für CompositeCommand.

Geschlossen 3. November 2009 um 6:28 Uhr von Dieses Problem wurde in der Prism-v2.1 Version behoben, so dass das Workitem jetzt geschlossen ist. Prism 2.1 kann hier heruntergeladen werden:
http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en

1

Ich denke, dass in diesem Code eine zirkuläre Referenz ist, die das Ansichtsmodell verursacht nie Müll gesammelt werden.

Ich weiß, das ist eine alte Frage, aber ich werde darauf hinweisen, dass einige Implementierungen von DelegateCommand oder RelayCommand eine WeakReference zu der Aktion halten. Die Verwendung des DelegateCommand ist hier typisch, führt aber leider zu Speicherverlusten bei dieser Implementierung, da, wenn die ViewModel-Methode in den DelegateCommand-Konstruktor übergeben wird, automatisch ein Verweis auf die Klasse, die diese Methode enthält, vom Delegaten erfasst wird.

Wenn Sie IDispose in Ihrem ViewModel implementiert und die Verweise auf die DelegateCommands explizit in Dispose gelöscht haben, können Sie diese Implementierung weiterhin verwenden. Ihre Ansicht, die Ihr ViewModel erstellt, müsste jedoch auch Dipose enthalten.

+0

Das Problem hier ist nicht 'DelegateCommand', was' ViewModel' daran hindert, Garbage Collected zu sein, sondern dass etwas (vielleicht 'WPF Control' oder' ViewModel' verhindert, dass 'DelegateCommand' von Garbage Collected erfasst wird). – Lightman

+1

Ja, weshalb Dispose zum ViewModel hinzugefügt werden muss, damit das ViewModel den Verweis auf DelegateCommand loswerden kann. Es gibt definitiv einen zirkulären Verweis hier zwischen dem DelegateCommand und dem ViewModel, aber ob einer von diesen verwurzelt ist und die Speicherbereinigung verhindert, weiß ich nicht. – Malaise

Verwandte Themen