これまでのところ、私のテストでは、Silverlight で MVVM パターンを利用するすべての標準的なアプローチ、例、およびフレームワークが、VM のガベージ コレクションを妨げる大規模なメモリ リークという大きな問題に悩まされていることが示されています。
明らかに、これは巨大でばかげた主張です - だから私の期待は、誰かが私が間違っている理由と場所について明白な答えを持っていることです:)
再現する手順は簡単です。
- ビューのデータ コンテキストを VM に設定して、ビューモデルをビューにバインドします (ビューモデルが INotifyPropertyChanged を利用してデータ バインディングをサポートすると仮定します)。
- ビューモデルのプロパティに UI 要素をバインドします。次に例を示します。
<TextBox Text="{Binding SomeText}" />
- 何らかの方法でバインディングを活用します (たとえば、テキスト ボックスに入力するだけです)。
これにより、ルートから BindingExpression、viewmodel に至る参照チェーンが作成されます。次に、VM へのすべての参照と同様に、UI ツリーからビューを削除できます。ただし、root<>BindingExpression<>VM 参照チェーンのおかげで、VM がガベージ コレクションされることはありません。
問題を示す 2 つの例を作成しました。新しいビュー/ビューモデルを作成するボタン (古いものへのすべての参照をダンプする必要があります) と、ガベージ コレクションを強制し、現在のメモリ使用量を報告するボタンがあります。
例 1 は、非常に簡素化されたカリバーン マイクロの例です。例 2 では、フレームワークを使用せず、考えられる最も単純な方法で問題を示しています。
サンプル プロジェクトをダウンロードしたくないが手助けしたい人のために、例 2 のコードを示します。FooViewModel と呼ばれるビューモデルから始めます。
public class FooViewModel : INotifyPropertyChanged
{
string _fooText;
public string FooText
{
get { return _fooText; }
set
{
_fooText = value;
NotifyPropertyChanged("FooText");
}
}
private byte[] _data;
public FooViewModel()
{
_data = new byte[10485760]; //use up 10mb of memory
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
これは、バインドする FooText という文字列プロパティを公開するだけです。INotifyPropertyChanged は、バインドを容易にするために必要です。
次に、以下を含むユーザーコントロールである FooView というビューがあります。
<UserControl x:Class="MVVMLeak.FooView">
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal">
<TextBlock Text="Bound textbox: " />
<TextBox Text="{Binding FooText}" Width="100"/>
</StackPanel>
</UserControl>
(簡潔にするために名前空間は省略されています)
ここで重要なのは、FooText プロパティにバインドされているテキスト ボックスです。もちろん、データ コンテキストを設定する必要があります。これは、ViewModelLocator を導入するのではなく、コード ビハインドで行うことにしました。
public partial class FooView : UserControl
{
public FooView()
{
InitializeComponent();
this.DataContext = new FooViewModel();
}
}
メインページは次のようになります。
<StackPanel x:Name="LayoutRoot" Background="White">
<Button Click="Button_Click" Content="Click for new FooView"/>
<Button Click="Button2_Click" Content="Click to garbage collect"/>
<ContentControl x:Name="myContent"></ContentControl>
</StackPanel>
コードビハインドで次のようにします。
private void Button_Click(object sender, RoutedEventArgs e)
{
myContent.Content = new FooView();
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Memory in use after collection: " + (GC.GetTotalMemory(true) / 1024 / 1024).ToString() + "MB");
}
注: 問題を再現するには、必要になるまでバインディング式が作成されないと思われるため、必ずテキスト ボックスに何かを入力してください。
この KB 記事が関連している可能性があることは注目に値しますが、「方法 2」の回避策は効果がないようで、参照チェーンが一致していないように見えるため、確信が持てません。
また、それが問題かどうかはわかりませんが、CLR Profilerを使用して原因を診断しました。
アップデート:
誰かがテストし、その結果をコメントで報告したい場合は、ここでドロップボックス経由で Silverlight アプリケーションをホストしています: Hosted Example . 再現するには: 一番上のボタンを押して、何かを入力して、一番上のボタンを押して、何かを入力して、一番上のボタンを押してください。次に、ボタンを押します。10MB の使用量 (または増加していないその他の量) が報告されている場合、メモリ リークは発生していません。
これまでのところ、12GB RAM、64 ビット Win 7 Enterprise を搭載した ThinkPad w510 (43192RU) であるすべての開発マシンで問題が発生しているようです。これには、開発ツールがインストールされていないものも含まれます。彼らが VMWare ワークステーションを実行していることは注目に値するかもしれません。
この問題は、私が試した他のマシンでは発生していないようです。これには、いくつかの自宅の PC やオフィスの他の PC が含まれます。SL バージョン、メモリ量、およびおそらく vmware を除外しました。いまだに原因が突き止められていません。