2010-03-29 287 views
10

更新:我在另一个更干净的机器上试过这个。我无法在该机器上重现这一点。如果我发现哪些违规(VSStudio)组件会导致此问题,我会通知您。在WPF:Children.Remove或Children.Clear不会释放对象

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

如何从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> 

后面的代码是:在C#

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

回答

3

对象不会自动“释放”,只要他们不再使用。

相反,当您从控件中删除对象时,它将变为符合垃圾回收条件,假设您没有其他引用该UIelement的地方。

一旦一个对象被“取消”(直接或间接地从应用程序中的任何已使用对象引用),它就有资格收集。垃圾收集器最终会清理你的对象,但是当这种情况发生时并不是你(通常)控制的东西。

只要相信它最终会被清理干净。这是C#(以及.NET的总称)的美妙之处 - 管理和担心这是为您处理的。


编辑:经过一些测试后,看起来窗口持有对UI元素的引用,直到下一个布局通过。您可以强制通过增加一个调用发生:

this.UpdateLayout(); 

从画布儿童删除元素(一个或多个)之后。这将导致对象可用于GC。

+0

我知道,注销已命名的类,但不这么认为在这种情况下。当我在MouseDown处理程序的开头添加GC.Collect()时,它应该选择符合垃圾收集条件的控件。它没有。我收集的内容仍然必须在Canvas对象内部引用它。 – 2010-03-29 22:47:23

+0

@Bart:尝试在处理程序的END处添加GC.Collection - 控件仍处于处理程序开始处的UI中...是否有帮助? – 2010-03-29 22:48:32

+0

@Bart:另外 - 尝试添加对GC.WaitForPendingFinalizers()的调用 - 请参阅:http://msdn.microsoft.com/en-us/library/system.gc.waitforpendingfinalizers.aspx – 2010-03-29 22:49:49

8

变化

public class MyControl : UserControl

public class MyControl : ContentControl

,它会说再见(第二次之后您删除控制)。我也验证了内存不漏用

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

此外,见this

通过清除grid.Children删除系统testControl,但它不是垃圾收集立即资格。它上面的几个异步操作正在等待处理,直到这些操作完成(包括引发渲染引擎中的Unloaded事件和一些清理代码)才能GC'd。

我验证了如果你等到这些操作完成(比如通过调度ContextIdle优先级的Dispatcher操作),TestControl才有资格使用GC,而不管TextBlock上是否存在绑定。

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

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

3

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

可以使用GC.Collect的()的参数,以迫使第三代的垃圾收集,
然而,最好的approuch是不调用GC.Collect()自己,
改用IDisposable接口和将Children绑定到ObservableCollection 并且当您收到任何已移除对象的CollectionChanged事件Dispose()时。

0

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

2

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

当您从其父项中移除UIElement时,该字典将保持对该控件的引用。

有一个博客帖子/视频在这里调试内存泄漏:WPF x:Name Memory Leak

解决办法是不使用X:姓名或以确保由X更让控件:名称将被清除出以免在收集视觉树的一部分之前消耗太多内存。

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