GC的固定实例 - 无法从我的托管代码中追溯

WPF*_*-it 9 .net c# wpf garbage-collection pinning

所以我使用WPF 3.5和MVVM + DataTemplate方法在GUI上加载2个视图.我在内存分析中观察到,作为项目控件的项容器的一部分生成的项目被固定到内存中,即使在卸载视图后也不会获得GCed!

我刚刚运行测试,发现即使对于最简单的代码,它也是可重现的...你们可以自己检查一下.

XAML:

<Window x:Class="ContentControlVMTest.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ContentControlVMTest"
        Title="Window2" Height="300" Width="300">
    <DockPanel LastChildFill="True">

        <CheckBox Click="CheckBox_Click" Content="Test1?"
                  DockPanel.Dock="Top" Margin="5"/>

        <ContentControl x:Name="contentControl">
            <ContentControl.Resources>

                <DataTemplate DataType="{x:Type local:Test3}">
                    <TextBlock Text="{Binding C}" Margin="5"/>
                </DataTemplate>

                <DataTemplate DataType="{x:Type local:Test1}">
                    <DockPanel LastChildFill="True" Margin="5">
                        <TextBlock Text="{Binding A}"
                                   DockPanel.Dock="Top"
                                   Margin="5"/>
                        <ListBox ItemsSource="{Binding Bs}"
                                 DisplayMemberPath="B"
                                 Margin="5"/>
                    </DockPanel>
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

代码背后:

public class Test3
{
    public string C { get; set; }
}

public class Test2
{
    public string B { get; set; }
}

public class Test1
{
    public string A { get; set; }

    private List<Test2> _Bs;
    public List<Test2> Bs
    {
        get
        {
            return _Bs;
        }

        set
        {
            _Bs = value;
        }
    }
}

public partial class Window2 : Window
{
    public Window2()
    {
        InitializeComponent();
        this.KeyDown += Window_KeyDown;
    }

    private void Window_KeyDown
            (object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (Keyboard.IsKeyDown(Key.LeftCtrl))
            if (Keyboard.IsKeyDown(Key.LeftShift))
                if (Keyboard.IsKeyDown(Key.LeftAlt))
                    if (Keyboard.IsKeyDown(Key.G))
                    {
                        GC.Collect(2, GCCollectionMode.Forced);
                        GC.WaitForPendingFinalizers();
                        GC.Collect(2, GCCollectionMode.Forced);
                        GC.WaitForPendingFinalizers();
                        GC.Collect(3, GCCollectionMode.Forced);
                        GC.WaitForPendingFinalizers();
                        GC.Collect(3, GCCollectionMode.Forced);
                    }
    }

    private void CheckBox_Click(object sender, RoutedEventArgs e)
    {
        if (((CheckBox)sender).IsChecked.GetValueOrDefault(false))
        {
            var x = new Test1() { A = "Test1 A" };
            x.Bs = new List<Test2>();
            for (int i = 1; i < 10000; i++ )
            {
                x.Bs.Add(new Test2() { B = "Test1 B " + i });
            }
            contentControl.Content = x;
        }
        else
        {
            contentControl.Content = new Test3() { C = "Test3 C" };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我通过左移+ Alt + Ctrl + G执行强制GC.Test1或者Test3视图和视图模型的所有项目在正确卸载后都会死亡.所以这是预期的.

但是在Test1模型中生成的集合(具有Test2对象)仍然固定在内存中.并且它表明该数组是列表框的items容器使用的数组,因为它显示了列表框中的去虚拟化项目的数量!当我们在Test1视图模式下最小化或恢复视图时,此固定数组会更改其大小!有一次它是16项,下一次它是69项描述时.

在此输入图像描述

这意味着WPF执行项目控件中生成的项目的固定!有谁能解释一下?这有什么明显的缺点吗?

多谢.

Nic*_*oiu 3

该问题是由于绑定机制未能完全释放实际已绑定以在屏幕上显示的列表项而引起的。最后一点几乎可以肯定是为什么您在不同的运行中看到不同数量的“孤立”实例。滚动列表越多,产生的问题就越多。

这似乎与我一年多前提交的错误报告中描述的同一类潜在问题有关,因为固定根和固定实例树是相似的。(要以方便的格式查看此类详细信息,您可能需要获取一个更高级的内存分析器的副本,例如ANTS Memory Profiler。)

真正的坏消息是,您的孤立实例被固定在窗口本身的消亡之后,因此如果没有我在 WinForms 场景中必须使用的相同类型的黑客来强制清理窗口,您可能无法清理它们。具有约束力的私人。

所有这一切中唯一的好消息是,如果您可以避免绑定到嵌套属性,则不会出现该问题。例如,如果您向 Test2 添加 ToString() 重写以返回其 B 属性的值并从列表框项中删除 DisplayMemberPath,则问题将会消失。例如:

public class Test2
{
    public string B { get; set; }

    public override string ToString()
    {
        return this.B;
    }
}
Run Code Online (Sandbox Code Playgroud)
<ListBox ItemsSource="{Binding Bs}" 
    Margin="5"/>
Run Code Online (Sandbox Code Playgroud)

  • @AngelWPF:您看到的 16 字节 Test2[] 数组不是泄漏的实例。它是由 List&lt;T&gt; 创建的静态实例,供容量为 0 时使用。您可以通过始终将非零初始容量传递给 List&lt;T&gt; 构造函数来避免创建它,但这是一种微优化,我不会这样做推荐。 (2认同)