9

そのため、MVVM + DataTemplate メソッドで WPF 3.5 を使用して、GUI に 2 つのビューをロードしています。アイテム コントロールのアイテム コンテナーの一部として生成されたアイテムがメモリに固定され、ビューがアンロードされた後でも GC されないことをメモリ プロファイリング中に観察しました。

テストを実行したところ、最も単純なコードでも再現可能であることがわかりました...自分で確認できます。

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>

コードビハインド:

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

左 Shift + Alt + Ctrl + G で強制 GC を実行します。Test1またはTest3ビューとビュー モデルのすべてのアイテムは、正しくアンロードされた後に無効になります。ということで、予想通りです。

Test1ただし、モデル (オブジェクトを含む) で生成されたコレクションはTest2、メモリに固定されたままです。また、リストボックスの仮想化されていない項目の数を示しているため、配列がリストボックスの項目コンテナーによって使用される配列であることを示しています! Test1この固定配列は、ビュー モードでビューを最小化または復元するとサイズが変わります。あるときは 16 アイテムで、次はプロファイリングすると 69 アイテムでした。

ここに画像の説明を入力

これは、WPF が項目コントロールで生成された項目の固定を実行することを意味します! 誰でもこれを説明できますか?これには重大な欠点がありますか?

ありがとう。

4

2 に答える 2

3

この問題は、実際に画面に表示するためにバインドされているリスト項目をバインド メカニズムが完全に解放できないことが原因で発生しています。その最後のビットが、実行ごとに異なる数の「孤立した」インスタンスが表示される理由であることはほぼ確実です。リストをスクロールすればするほど、より多くの問題が発生します。

これは、固定ルート ツリーと固定インスタンス ツリーが似ているため、1 年以上前に提出したバグ レポートに記載されているのと同じ種類の根本的な問題に関連しているようです。(この種の詳細を便利な形式で表示するには、ANTS Memory Profilerのようなやや凝ったメモリ プロファイラのコピーを取得することをお勧めします。)

本当に悪いニュースは、孤立したインスタンスがウィンドウ自体の終焉を過ぎて固定されていることです。そのため、WinForms シナリオで強制的にクリーンアップするために使用しなければならなかったのと同じ種類のハックなしでは、おそらくそれらをクリーンアップすることはできません。拘束力のあるプライベート。

唯一の朗報は、入れ子になったプロパティへのバインドを回避できれば、問題は発生しないということです。たとえば、ToString() オーバーライドを Test2 に追加して B プロパティの値を返し、リストボックス項目から DisplayMemberPath を削除すると、問題は解消されます。例えば:

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

    public override string ToString()
    {
        return this.B;
    }
}

<ListBox ItemsSource="{Binding Bs}" 
    Margin="5"/>
于 2012-02-24T20:19:59.077 に答える
0

上記のサンプル コードでは、ビジュアルをアンロードしている場所がわかりません。

しかし、ビュー全体をアンロードしていると仮定すると、これはまだ予測可能です。考慮していない要因は Dispatcher です。Dispatcher は優先順位付けされたイベント キューであり、そのキュー内のすべてのデリゲートに対して、それらのデリゲートが指すオブジェクトへの参照を維持します。つまり、ビュー内の何かが Unloaded イベントの後にキューにある可能性が非常に高く、したがって GC で正当な参照が行われる可能性があります。顔が青くなるまでGC.Collectを実行できますが、参照が残っているオブジェクトを収集することはありません。

したがって、ディスパッチャをポンピングしてから GC.Collect を呼び出す必要があります。このようなもの:

void Control_Unloaded(object sender, RoutedEventArgs e)
{
  // flush dispatcher
  this.Dispatcher.BeginInvoke(new Action(DoMemoryAnalysis), DispatcherPriority.ContextIdle);
}

private static void DoMemoryAnalysis()
{
  GC.Collect();
  GC.WaitForPendingFinalizers();

  // do memory analysis now.
}

.net でのメモリ リークのもう 1 つのよくある原因は、イベントのアタッチとアタッチの解除が正しく行われていないことに関係しています。上記のサンプルではこれを行っているとは思いませんが、イベントを添付している場合は、Unloaded または Dispose または最も適切な場所でそれらを添付解除していることを確認してください。

于 2012-02-23T18:04:18.720 に答える