Jump to content

WPF bindings resultam em memory leaks?


edmolko

Recommended Posts

Boas!

Eu fiz uma aplicação em WPF de teste para tentar perceber porque é que as meus child viewmodels não são destruídos.

A aplicação consiste numa view principal (main) que tem 3 botões: cada botão muda o child viewmodel. O problema é que o destrutor do child view model só é chamado quando o utilizador navega 2 vezes depois.

Por exemplo:

  1. O child viewmodel inicial é o SolidWoods_VM;
  2. O utilizador clica em "Show Boards";
  3. O destrutor de SolidWoods_VM não é chamado;
  4. O utilizador clica em "Show Acessories";
  5. O destrutor de SolidWoods é chamado;
  6. O destrutor de Boards_VM não é chamado;

Código:

Main View:

<Window.DataContext>
<my:Main_VM />
</Window.DataContext>

<Window.Resources>
<DataTemplate DataType="{x:Type my:SolidWoods_VM}">
	<my:SolidWoods />
</DataTemplate>
<DataTemplate DataType="{x:Type my:Boards_VM}">
	<my:Boards />
</DataTemplate>
<DataTemplate DataType="{x:Type my:Acessories_VM}">
	<my:Acessories />
</DataTemplate>
</Window.Resources>

<StackPanel>
<Button Content="Show Solidwoods" Command="{Binding NavSolidWoods}"/>
<Button Content="Show Boards" Command="{Binding NavBoards}"/>
<Button Content="Show Acessories" Command="{Binding NavAcessories}"/>
<ContentControl Content="{Binding CurrentView}" />
</StackPanel>

Main viewmodel:

public class Main_VM : ViewModelBase
{
#region Fields

private object _currentView;
public object CurrentView
{
	get { return _currentView; }
	set
	{
		if (_currentView != value)
		{
			_currentView = value;
			NotifyPropertyChanged("CurrentView");
		}
	}
}

#endregion

#region Commands

#region Declarations

private DelegateCommand _navSolidWoods;
public DelegateCommand NavSolidWoods
{
	get
	{
		if (_navSolidWoods == null)
			_navSolidWoods = new DelegateCommand(ShowSolidWoods);
		return _navSolidWoods;
	}
}

private DelegateCommand _navBoards;
public DelegateCommand NavBoards
{
	get
	{
		if (_navBoards == null)
			_navBoards = new DelegateCommand(ShowBoards);
		return _navBoards;
	}
}

private DelegateCommand _navAcessories;
public DelegateCommand NavAcessories
{
	get
	{
		if (_navAcessories == null)
			_navAcessories = new DelegateCommand(ShowAcessories);
		return _navAcessories;
	}
}

#endregion

#region Actions

private void ShowSolidWoods(object o)
{
	CurrentView = null;
	GC.Collect();
	CurrentView = new SolidWoods_VM();
}

private void ShowBoards(object o)
{
	CurrentView = null;
	GC.Collect();
	CurrentView = new Boards_VM();
}

private void ShowAcessories(object o)
{
	CurrentView = null;
	GC.Collect();
	CurrentView = new Acessories_VM();
}

#endregion

#endregion

public Main_VM()
{
	CurrentView = new SolidWoods_VM();
}

Exemplo de um childviewmodel:

public class SolidWoods_VM
{
public SolidWoods_VM()
{

}

~SolidWoods_VM()
{
	Console.WriteLine("SolidWoods_VM is dying...");
}
}
Link to comment
Share on other sites

Não tive tempo de pesquisar este caso em particular, porém a recomendação geral em .net é deixar sempre o garbage collector fazer o gerenciamento da memória e nunca chamar o GC.collect manualmente e também utilizar IDisposable ao invés de destrutores/desconstrutores. Existe um ebook do Ricky Leeks disponível gratuitamente que explica a razão disto. Procure por ".net memory management Ricky Leeks". Também existe um podcast do autor no .net rocks que é bem interessante.

Fernando Lage Bastos - MCP/MCTS/MCPD

Link to comment
Share on other sites

Sim, tenho

/// <summary>
   /// This class is an implementation detail of the MessageToActionsMap class.
   /// </summary>
   internal class WeakAction
   {
    readonly MethodInfo method;
    readonly Type delegateType;
    readonly WeakReference weakRef;
    /// <summary>
    /// Constructs a WeakAction
    /// </summary>
    /// <param name="target">The instance to be stored as a weak reference</param>
    /// <param name="method">The Method Info to create the action for</param>
    /// <param name="parameterType">The type of parameter to be passed in the action. Pass null if there is not a paramater</param>
    internal WeakAction(object target, MethodInfo method, Type parameterType)
    {
	    //create a Weakefernce to store the instance of the target in which the Method resides
	    weakRef = new WeakReference(target);
	    this.method = method;
  // JAS - Added logic to construct callback type.
  if (parameterType == null)
   this.delegateType = typeof(Action);
  else
   this.delegateType = typeof(Action<>).MakeGenericType(parameterType);
    }
    /// <summary>
    /// Creates a "throw away" delegate to invoke the method on the target
    /// </summary>
    /// <returns></returns>
 internal Delegate CreateAction()
 {
  object target = weakRef.Target;
  if (target != null)
  { 
   // Rehydrate into a real Action
   // object, so that the method
   // can be invoked on the target.
   return Delegate.CreateDelegate(
   this.delegateType,
   weakRef.Target,
   method);
  }
  else
  {
   return null;
  }
 }
    /// <summary>
    /// returns true if the target is still in memory
    /// </summary>
    public bool IsAlive
    {
	    get
	    {
		    return weakRef.IsAlive;
	    }
    }
   }

MessageToActionsMap

/// <summary>
   /// This class is an implementation detail of the Mediator class.
   /// This will store all actions to be invoked
   /// </summary>
   internal class MessageToActionsMap
   {
    //store a hash where the key is the message and the value is the list of Actions to call
    readonly Dictionary<string, List<WeakAction>> map = new Dictionary<string, List<WeakAction>>();
    /// <summary>
    /// Adds an action to the list
    /// </summary>
    /// <param name="message">The message to register to </param>
    /// <param name="target">The target object to invoke</param>
    /// <param name="method">The method in the target object to invoke</param>
    /// <param name="actionType">The Type of the action</param>
    internal void AddAction(string message, object target, MethodInfo method, Type actionType) 
    {
	    if (message == null)
		    throw new ArgumentNullException("message");
  if (method == null)
		    throw new ArgumentNullException("method");
	    lock (map)//lock on the dictionary
	    {
		    if (!map.ContainsKey(message))
			    map[message] = new List<WeakAction>();
		    map[message].Add(new WeakAction(target, method, actionType));
	    }
    }
    /// <summary>
    /// Gets the list of actions to be invoked for the specified message
    /// </summary>
    /// <param name="message">The message to get the actions for</param>
    /// <returns>Returns a list of actions that are registered to the specified message</returns>
    internal List<Delegate> GetActions(string message)
    {
	    if (message == null)
		    throw new ArgumentNullException("message");
	    List<Delegate> actions;
	    lock (map)
	    {
		    if (!map.ContainsKey(message))
			    return null;
		    List<WeakAction> weakActions = map[message];
		    actions = new List<Delegate>(weakActions.Count);
		    for (int i = weakActions.Count - 1; i > -1; --i)
		    {
			    WeakAction weakAction = weakActions[i];
			    if (!weakAction.IsAlive)
				    weakActions.RemoveAt(i);
			    else
				    actions.Add(weakAction.CreateAction());
		    }
		    //delete the list from the hash if it is now empty
		    if (weakActions.Count == 0)
			    map.Remove(message);
	    }
	    return actions;
    }
   }

No entanto a condição if(!weakAction.IsAlive) é sempre falsa porque IsAlive é sempre = true;

No 1º exemplo deste tópico nem sequer estou a utilizar a mediator pattern.

O que eu acho é que o binding no ficheiro .xaml, de alguma forma mantém uma referência ao child viewmodel que eu não sei como remover.

<ContentControl Content="{Binding CurrentView}" />
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.