2010-03-29 288 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!