Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In WPF: Children.Remove or Children.Clear doesn't free objects

Update: I tried this on another, more cleanly installed, machine. I could not reproduce this on that machine. If I find out what offending (VSStudio) component causes this, I will let you know.

I create some UIElements from code behind and was anticipating the garbage collection to clear up stuff. However, the objects are not free-ed at the time I expected it. I was expecting them to be freeed at RemoveAt(0), but they are only freed at the end of the program.

How can I make the objects be freed when removed from the Children collection of the Canvas?

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
    MouseDown="Window_MouseDown">
  <Grid>
    <Canvas x:Name="main" />
  </Grid>
</Window>

The code behind is:

public partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
  }

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
  GC.Collect(); // This should pick up the control removed at a previous MouseDown
  GC.WaitForPendingFinalizers(); // Doesn't help either

  if (main.Children.Count == 0)
    main.Children.Add(new MyControl() { Background = Brushes.Yellow, Width = 100, Height = 50 });
  else
    main.Children.RemoveAt(0);
 }
}

public class MyControl : UserControl
{
  ~MyControl()
  {
    Debug.WriteLine("Goodbye");
  }
}
like image 589
Bart Roozendaal Avatar asked Mar 29 '10 22:03

Bart Roozendaal


3 Answers

You may find this of interest. I found out recently that x:Name markup extension stores a reference to the UIElement in the parent control in a dictionary keyed by the string name.

When you remove the UIElement from its parent, the dictionary keeps a reference to the control.

There's a blog post / video debugging the memory leak here: WPF x:Name Memory Leak

The solution is to not use x:Name or to ensure that controls that are kept alive by x:Name are cleared out so as not to consume too much memory before a section of the visual tree is collected.

Update: You deregister a named class using the NameScope

this.grid.Children.Remove(child); // Remove the child from visual tree
NameScope.GetNameScope(this).UnregisterName("child"); // remove the keyed name
this.child = null; // null the field

// Finally it is free to be collected! 
like image 61
Dr. Andrew Burnett-Thompson Avatar answered Oct 23 '22 14:10

Dr. Andrew Burnett-Thompson


Change

public class MyControl : UserControl

to

public class MyControl : ContentControl

and it will say goodbye (after the second time you remove the control.) I also verified the memory does not leak by using

Debug.WriteLine("mem: " + GC.GetTotalMemory(true).ToString());

Also, see this:

You remove the TestControl by clearing grid.Children, but it is not immediately eligible for garbage collection. Several asynchronous operations on it are pending, and it cannot be GC'd until those operations complete (these include raising the Unloaded event and some cleanup code in the rendering engine).

I verified that if you wait until these operations complete (say by scheduling a Dispatcher operation at ContextIdle priority), the TestControl becomes eligible for GC, independent of the presence of a binding on the TextBlock.

UserControl must either have a internal event that doesn't clean up quickly, or it might be a bug with VS2010 RC. I'd report this through connect, but for now switch to ContentControl.

Since you're using UserControl, I assume you'll also have to switch to using the Generic.xaml template. That isn't too difficult of a change-over (and for most things is a better solution.)

like image 8
Joshua Blake Avatar answered Oct 23 '22 12:10

Joshua Blake


There are 3 generations of garbage collection in C#, so even if there are no references to your objects, it could take 3 garbage collections to free them.

You can use the GC.Collect()'s parameter to force a 3rd generation garbage collection,
however, the best approuch is to not call GC.Collect() yourself,
instead use the IDisposable interface and bind the Children to an ObservableCollection and when you get a CollectionChanged event Dispose() of any removed objects.

like image 4
Danny Varod Avatar answered Oct 23 '22 13:10

Danny Varod