问题 在WPF中:Children.Remove或Children.Clear不释放对象


更新:我在另一台安装得更干净的机器上尝试了这个。我无法在那台机器上重现这一点。如果我发现有什么违规(VSStudio)组件导致这种情况,我会告诉你。

我从后面的代码创建一些UIElements,并期待垃圾收集来清理东西。但是,在我预期的时候,对象不会被释放。我期待他们在RemoveAt(0)被释放,但他们只在程序结束时被释放。

从画布的儿童系列中删除时,如何释放对象?

<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>

背后的代码是:

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");
  }
}

6524
2018-03-29 22:31


起源



答案:


更改

public class MyControl : UserControl

public class MyControl : ContentControl

并且它将说再见(在第二次移除控件之后。)我还通过使用验证了内存不泄漏

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

另外,请参阅 这个

您通过清除grid.Children删除TestControl,但它不能立即符合垃圾回收的条件。它上面的几个异步操作正在等待,并且在这些操作完成之前不能进行GC操作(这些操作包括在渲染引擎中引发Unloaded事件和一些清理代码)。

我验证了如果等到这些操作完成(比如通过在ContextIdle优先级上安排Dispatcher操作),TestControl就有资格获得GC,而不管TextBlock上是否存在绑定。

UserControl必须具有不能快速清理的内部事件,或者它可能是VS2010 RC的错误。我通过connect报告这个,但是现在切换到ContentControl。

由于您正在使用UserControl,我假设您还必须切换到使用Generic.xaml模板。转换并不太难(对于大多数事情来说,这是一个更好的解决方案。)


8
2018-03-30 00:13





C#中的对象一旦不再使用就不会自动“释放”。

相反,当您从Control中删除对象时,它就会变为 有资格进行垃圾收集 此时,假设您没有对该UIelement的其他引用。

一旦对象被“无根”(没有直接或间接地从您的应用程序中的任何使用对象引用),它就有资格进行收集。然后,垃圾收集器将最终清理您的对象,但是当发生这种情况时,您(通常)无法控制。

只要相信它最终会得到清理。这是C#(和.NET一般)的美妙之处 - 管理和担心,这是为您处理的。


编辑:经过一些测试后,看起来Window保持对UIelement的引用,直到下一个布局传递。您可以通过添加对以下内容的调用来强制执行此操作:

this.UpdateLayout();

从画布中删除元素后的子项。这将导致对象可用于GC。


3
2018-03-29 22:43



我知道,但在这种情况下不要这么认为。当我在MouseDown处理程序的开头添加GC.Collect()时,它应该选择符合垃圾收集条件的控件。它没有。我聚集在那里仍然必须在Canvas对象内部引用它。 - Bart Roozendaal
@Bart:尝试在处理程序的END处添加GC.Collection - 控件仍在处理程序开头的UI中...这有帮助吗? - Reed Copsey
@Bart:另外 - 尝试添加对GC.WaitForPendingFinalizers()的调用 - 请参阅: msdn.microsoft.com/en-us/library/... - Reed Copsey
这并不重要。如果继续单击,则会一直创建和删除对象。这些都没有被释放,但在程序结束时。 - Bart Roozendaal
尝试添加WaitForPendingFinalizers:它没有帮助... - Bart Roozendaal


答案:


更改

public class MyControl : UserControl

public class MyControl : ContentControl

并且它将说再见(在第二次移除控件之后。)我还通过使用验证了内存不泄漏

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

另外,请参阅 这个

您通过清除grid.Children删除TestControl,但它不能立即符合垃圾回收的条件。它上面的几个异步操作正在等待,并且在这些操作完成之前不能进行GC操作(这些操作包括在渲染引擎中引发Unloaded事件和一些清理代码)。

我验证了如果等到这些操作完成(比如通过在ContextIdle优先级上安排Dispatcher操作),TestControl就有资格获得GC,而不管TextBlock上是否存在绑定。

UserControl必须具有不能快速清理的内部事件,或者它可能是VS2010 RC的错误。我通过connect报告这个,但是现在切换到ContentControl。

由于您正在使用UserControl,我假设您还必须切换到使用Generic.xaml模板。转换并不太难(对于大多数事情来说,这是一个更好的解决方案。)


8
2018-03-30 00:13





C#中的对象一旦不再使用就不会自动“释放”。

相反,当您从Control中删除对象时,它就会变为 有资格进行垃圾收集 此时,假设您没有对该UIelement的其他引用。

一旦对象被“无根”(没有直接或间接地从您的应用程序中的任何使用对象引用),它就有资格进行收集。然后,垃圾收集器将最终清理您的对象,但是当发生这种情况时,您(通常)无法控制。

只要相信它最终会得到清理。这是C#(和.NET一般)的美妙之处 - 管理和担心,这是为您处理的。


编辑:经过一些测试后,看起来Window保持对UIelement的引用,直到下一个布局传递。您可以通过添加对以下内容的调用来强制执行此操作:

this.UpdateLayout();

从画布中删除元素后的子项。这将导致对象可用于GC。


3
2018-03-29 22:43



我知道,但在这种情况下不要这么认为。当我在MouseDown处理程序的开头添加GC.Collect()时,它应该选择符合垃圾收集条件的控件。它没有。我聚集在那里仍然必须在Canvas对象内部引用它。 - Bart Roozendaal
@Bart:尝试在处理程序的END处添加GC.Collection - 控件仍在处理程序开头的UI中...这有帮助吗? - Reed Copsey
@Bart:另外 - 尝试添加对GC.WaitForPendingFinalizers()的调用 - 请参阅: msdn.microsoft.com/en-us/library/... - Reed Copsey
这并不重要。如果继续单击,则会一直创建和删除对象。这些都没有被释放,但在程序结束时。 - Bart Roozendaal
尝试添加WaitForPendingFinalizers:它没有帮助... - Bart Roozendaal


在C#中有3代垃圾收集,因此即使没有对象的引用,也可能需要3个垃圾收集来释放它们。

您可以使用GC.Collect()的参数强制执行第3代垃圾回收,
但是,最好的approuch是不要自己调用GC.Collect(),
而是使用IDisposable接口并将Children绑定到ObservableCollection 当你得到任何被删除对象的CollectionChanged事件Dispose()时。


3
2018-03-30 00:21





你可能会发现这个有趣的。我最近发现x:Name标记扩展在由字符串名称键入的字典中存储父控件中UIElement的引用。

从其父项中删除UIElement时,字典将保留对该控件的引用。

这里有一篇博文/视频调试内存泄漏: WPF   x:名称内存泄漏

解决方案是不使用x:Name或确保清除x:Name保持活动的控件,以便在收集可视树的一部分之前不占用太多内存。

更新: 您使用。取消注册命名类 名称范围

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! 

2
2018-06-02 09:51





我们有同样的问题,也认为这可能是原因。但我们使用Memory Profiler工具分析了我们的项目,发现与Grid.Remove或Grid.RemoveAt无关。所以我认为我的建议就是在内存分析器工具中查看你的项目,看看你的项目中发生了什么。希望这有帮助。


0
2018-03-30 04:13