9

これまでのところ、私のテストでは、Silverlight で MVVM パターンを利用するすべての標準的なアプローチ、例、およびフレームワークが、VM のガベージ コレクションを妨げる大規模なメモリ リークという大きな問題に悩まされていることが示されています。

明らかに、これは巨大でばかげた主張です - だから私の期待は、誰かが私が間違っている理由と場所について明白な答えを持っていることです:)

再現する手順は簡単です。

  • ビューのデータ コンテキストを VM に設定して、ビューモデルをビューにバインドします (ビューモデルが INotifyPropertyChanged を利用してデータ バインディングをサポートすると仮定します)。
  • ビューモデルのプロパティに UI 要素をバインドします。次に例を示します。

<TextBox Text="{Binding SomeText}" />

  • 何らかの方法でバインディングを活用します (たとえば、テキスト ボックスに入力するだけです)。

これにより、ルートから BindingExpression、viewmodel に至る参照チェーンが作成されます。次に、VM へのすべての参照と同様に、UI ツリーからビューを削除できます。ただし、root<>BindingExpression<>VM 参照チェーンのおかげで、VM がガベージ コレクションされることはありません。

問題を示す 2 つの例を作成しました。新しいビュー/ビューモデルを作成するボタン (古いものへのすべての参照をダンプする必要があります) と、ガベージ コレクションを強制し、現在のメモリ使用量を報告するボタンがあります。

例 1 は、非常に簡素化されたカリバーン マイクロの例です。例 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 を除外しました。いまだに原因が突き止められていません。

4

2 に答える 2

4

解決策はまだ見つかっていませんが、問題は特定されています。この動作は、Silverlightsの自動化機能が次の理由で呼び出された場合に発生します。

  • タブレットPC入力サービス(つまり、すべての「タブレットのような」PC)
  • 自動テストツール
  • スクリーンリーダー(およびその他のアクセシビリティソフトウェア)

詳細はこちら:http ://www.wintellect.com/cs/blogs/sloscialo/archive/2011/04/13/silverlight-memory-leaks-and-automationpeers.aspx

したがって、新しい問題が表面化します。自動化ピアを無効にする方法、またはその他の方法で正しくクリーンアップする方法を教えてください。

この投稿は、1つのアプローチを示しています:WPFUserControlメモリリーク

ただし、複雑なコントロールのコントロールテンプレートは言うまでもなく、バインディングを使用する予定のすべてのSilverlightコントロールをオーバーライドする必要があるため、これは実際には実行可能なソリューションではありません。

誰かが良い解決策を特定できれば答えを変更しますが、今のところ解決策はないようです...

編集:

これは、仕事をしているように見えるちょっとした回避策です。Silverlightオブジェクトを定義するHTMLに次のパラメーターを追加するだけです。

<param name="windowless" value="true" />

「ウィンドウレス」モードで実行することの副作用は、自動化が機能しないことです:)

于 2012-06-23T20:34:00.287 に答える
0

2 番目の例では、メモリ リークはありません。

usingに新しいFooViewインスタンスを適用すると、View + ViewModel オブジェクト グラフ全体への参照が使用されなくなります。ContentControlmyContent.Content = new FooView();

最終的には、必要に応じてガベージコレクションされます。

おそらく、メモリ リークがあると思われる原因 (つまり、統計、再現手順など) について明確にする必要があります。

于 2012-05-27T12:20:56.800 に答える