2012-02-21 4 views
9

したがって、MVVM + DataTemplateメソッドでWPF 3.5を使用してGUIで2つのビューをロードしています。アイテムコントロールのアイテムコンテナの一部として生成されたアイテムがメモリに固定され、ビューがアンロードされた後でもGCedが取得されないというメモリプロファイリングを観察しました!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によってTest1またはTest3ビューのためにすべてのアイテムを強制的にGCを実行し、ビューモデルが正しくアンロードされた後に死んでしまいます。だからそれは期待通りです。

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

enter image description here

これは、WPFは、項目のコントロールで生成されたアイテムのピン止めを行い、意味!誰もこれを説明できますか?これには重大な欠点がありますか?

Thxたくさん。

+0

おそらく 'CollectionView'がうろついているコレクションのために作成しました。応答のための – user7116

+0

thx。はい!アイテムコンテナからのアイテムコレクションです。しかし、なぜそれが周りにぶら下がっているだろうか?見えなくなったListBoxはなくなっています。なぜWPFは連邦議会でコレクションをピン止めするのだろうか? –

+0

固定インスタンスのルートを投稿してください。 –

答えて

3

問題は、実際に画面に表示するためにバインドされたリスト項目を完全に解放するバインディングメカニズムが失敗したために発生しています。その最後のビットは、ほぼ確実に、異なる数の "孤立した"インスタンスが別の実行で表示されている理由です。リストをスクロールするほど、問題が多く発生します。

これは、固定ルートと固定インスタンスツリーが類似しているため、a bug report that I submitted over a year agoに記載されているのと同じ種類の根本的な問題に関連しているようです。 (そのような詳細を便利な形式で表示するには、ANTS Memory Profilerのようなやや好きなメモリプロファイラーのコピーを取りたいかもしれません)。

本当に悪いニュースは、あなたの孤立したインスタンスが、ウィンドウのシナリオで使用したのと同じ種類のハックがなければ、おそらくそれらをクリーンアップすることはできません。

ネストされたプロパティにバインドするのを避けることができれば、この問題は発生しません。たとえば、Bのプロパティの値を返すToString()のオーバーライドをTest2に追加し、リストボックスアイテムからDisplayMemberPathを削除すると、問題はなくなります。例えば:

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

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

<ListBox ItemsSource="{Binding Bs}" 
    Margin="5"/> 
+1

ああ、悪名高いバインディングリークの問題。これも真実です、私はそれを見ました。あなたのモデルにINotifyPropertyChangedを実装することで修正できます。 PropertyChangedイベントを決して呼び出さなくても、その存在がフレームワークの問題を修正します。 バインディングでは、Mode = OneWayまたはMode = OneTimeを追加してバインディングリークの問題を修正することもできます。これは常に利用可能ではありませんが。両方を試して、INotifyPropertyChangedと{Binding B、Mode = OneWay}を追加してください。 –

+0

@Nicole&Justin、Thxあなたの助けにたくさん。 ToString()またはOneTime Bindingを使用すると、孤立したTest2インスタンスが修正されました。もう一度Thx。しかし、配列Test2 []の1つのインスタンスはまだ固定されており、16バイトのメモリを占有しています。これは "n"個のTest2インスタンスをメモリに固定しておくよりもはるかに優れていますが、それでも削除する方法はありますか?私は 'Bs'コレクションの' INotifyPropertyChanged'も試みました( 'Bs'のOneTimeバインディングも疲れました)...配列は依然として残っています。どんな助け? –

+0

@ justin.m.chase:INotifyPropertyChangedの回避策に感謝します。私の以前のWinFormsの問題ではうまくいかなかったので、私はWPFのためにそれについて言及したことを完全に忘れてしまった。 –

0

上記のサンプルコードでは、ビジュアルをどこからアンロードするのかわかりません。

ただし、ビュー全体をアンロードすると仮定すると、これはまだ予測可能です。あなたが考慮していない要因はディスパッチャーです。 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でのメモリリークのもう本当に一般的な原因は、イベントを添付して、それらを正しくunattachingないに関係しています。上記のサンプルでこれを行うことはありませんが、イベントをアタッチしている場合は、UnloadedまたはDispose、または最も適切な場所にアタッチしていることを確認してください。

+0

MVVM + DataTemplateモデルでは、ビュー、ビューモデル、またはモデルオブジェクトがmemeory内に保持されません。異なるターゲットビューモデル設定されています。私のスナップショットは 'Test1'ビューがライブではないことを示しています!だからあなたの最初のクエリに答える...プラス私は、**固定された**インスタンスのルートがプロファイラによって(そのようなイベントハンドラに)追跡できないような漏れイベントハンドラはありません。私のコードに見られるように、WPFが何らかの理由で参照を保持しない限り、実際には処理されるイベントはありません。プラス**ピンニング**のインスタンスは常に審議されています。そのようなコードはありません。 –

+0

WPFは、すべての種類のイベントのディスパッチャキューにビジュアルを配置します。これが発生するには、コード内にイベントを接続する必要はありません。 Expression BlendでPSDインポータを使って作業しているときも、まったく同じ問題がありました。大きなpsdファイルを開き、ダイアログを閉じてもう一度開き、メモリ不足の例外でクラッシュします。窓が開いていないと、どうやって記憶に残ることができますか?ウィンドウがかなりの時間ディスパッチャキューに存在するためです。たとえビジュアルがもはやビジュアルツリーにもなくても、キューとGCを収集する必要があります。それを試してみてください。 –

関連する問題