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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2541684/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 14:07:19  来源:igfitidea点击:

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

.netwpfmemory-managementgarbage-collection

提问by Bart Roozendaal

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.

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

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.

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

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

从 Canvas 的 Children 集合中删除时,如何释放对象?

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

回答by Joshua Blake

Change

改变

public class MyControl : UserControl

public class MyControl : UserControl

to

public class MyControl : ContentControl

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());

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.

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

我验证过,如果您等到这些操作完成(比如通过在 ContextIdle 优先级安排 Dispatcher 操作),TestControl 将有资格进行 GC,而与 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.

UserControl 必须有一个不能快速清理的内部事件,或者它可能是 VS2010 RC 的错误。我会通过连接报告这个,但现在切换到 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.)

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

回答by Dr. ABT

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.

您可能会对此感兴趣。我最近发现 x:Name 标记扩展在以字符串名称为键的字典中存储了对父控件中 UIElement 的引用。

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

当您从其父级中移除 UIElement 时,字典会保留对该控件的引用。

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

这里有一篇博客文章/视频调试内存泄漏: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.

解决方法是不使用 x:Name 或确保通过 x:Name 保持活动的控件被清除,以免在收集可视化树的一部分之前消耗太多内存。

Update:You deregister a named class using the NameScope

更新:您使用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! 

回答by Danny Varod

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.

C# 中有 3 代垃圾回收,因此即使没有对您的对象的引用,也可能需要 3 次垃圾回收才能释放它们。

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.

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

回答by Reed Copsey

Objects in C# are not automatically "freed" as soon as they are no longer used.

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

Rather, when you remove the object from your Control, it becomes eligible for garbage collectionat that point, assuming you have no other references to that UIelement.

相反,当您从 Control 中删除该对象时,假设您没有对该 UIelement 的其他引用,则此时它可以进行垃圾回收

Once an object is "unrooted" (there are no references, directly or indirectly, from any used object in your application), it becomes eligible for collection. The garbage collector will then, eventually, clean up your object, but when this happens is not something you (typically) control.

一旦对象“无根”(没有直接或间接来自应用程序中任何使用过的对象的引用),它就符合收集条件。垃圾收集器最终会清理您的对象,但发生这种情况时,您(通常)无法控制。

Just trust that it will eventually get cleaned up. This is the beauty of C# (and .NET in general) - the management, and worry, of this is handled for you.

只要相信它最终会被清理干净。这就是 C#(以及一般的 .NET)的美妙之处——它的管理和烦恼都为您处理。



Edit: After some testing, it appears that the Window holds a reference to the UIelement until the next layout pass. You can force this to occur by adding a call to:

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

this.UpdateLayout();

After removing the element(s) from the canvas Children. This will cause the objects to be available for GC.

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

回答by Kishore Kumar

We had the same problem and also thought that this may be the reason. But we analyzed our project using Memory profiler tools and found that there is nothing to do with the Grid.Remove or Grid.RemoveAt. So i think my suggestion is just have a look your project at the memory profiler tool and see what is happening inside your project. hopes this helps.

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