次の方法で共有


メモリ リークの回避

マネージド XAML アプリケーションで Win2D コントロールを使用する場合は、ガベージ コレクターによってこれらのコントロールが再利用されるのを防ぐ可能性のある参照カウント サイクルを回避するように注意する必要があります。

〜であれば問題があります

  • (ネイティブ C++ ではなく) C# などの .NET 言語から Win2D を使用している
  • Win2D XAML コントロールのいずれかを使用します。
  • Win2D コントロールのイベント (たとえば、 DrawCreateResourcesSizeChangedなど) をサブスクライブします。
  • アプリが複数の XAML ページ間を行き来する

これらの条件がすべて満たされると、参照カウント サイクルによって Win2D コントロールがガベージ コレクションされなくなります。 新しい Win2D リソースは、アプリが別のページに移動するたびに割り当てられますが、古いものは解放されないため、メモリがリークされます。 これを回避するには、サイクルを明示的に中断するコードを追加する必要があります。

修正方法

参照カウント サイクルを中断し、ページがガベージ コレクションされるようにするには:

  • Win2D コントロールを含む XAML ページの Unloaded イベントをフックする
  • Unloaded ハンドラーで、Win2D コントロールのRemoveFromVisualTreeを呼び出します。
  • Unloaded ハンドラーで、Win2D コントロールへの明示的な参照を (nullに設定して) 解放します

コード例:

void page_Unloaded(object sender, RoutedEventArgs e)
{
    this.canvas.RemoveFromVisualTree();
    this.canvas = null;
}

作業例については、 サンプル ギャラリー のデモ ページを参照してください。

サイクル リークをテストする方法

アプリケーションが refcount サイクルを正しく中断しているかどうかをテストするには、Win2D コントロールを含む XAML ページにファイナライザー メソッドを追加します。

~MyPage()
{
    System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}

App コンストラクターで、ガベージ コレクションが定期的に発生することを確認するタイマーを設定します。

var gcTimer = new DispatcherTimer();
gcTimer.Tick += (sender, e) => { GC.Collect(); };
gcTimer.Interval = TimeSpan.FromSeconds(1);
gcTimer.Start();

ページに移動し、そのページから他のページに移動します。 すべてのサイクルが破損している場合は、Visual Studio の出力ウィンドウに 1~ 2 秒以内に Debug.WriteLine 出力が表示されます。

GC.Collectの呼び出しは中断を伴い、パフォーマンスが低下するため、リークのテストが完了したらすぐにこのテスト コードを削除する必要があることに注意してください。

血なまぐさい詳細

サイクルは、オブジェクト A が B への参照を持ち、B が A への参照を持つのと同時に発生します。または、A が B を参照し、B が C を参照し、C が A などを参照する場合。

XAML コントロールのイベントをサブスクライブする場合、この種のサイクルはかなり避けられません。

  • XAML ページには、その中に含まれるすべてのコントロールへの参照が保持されます
  • コントロールは、イベントにサブスクライブされているハンドラー デリゲートへの参照を保持します。
  • 各デリゲートは、そのターゲット インスタンスへの参照を保持します
  • イベント ハンドラーは通常、XAML ページ クラスのインスタンス メソッドであるため、ターゲット インスタンス参照は XAML ページを指し示し、サイクルを作成します。

関連するすべてのオブジェクトが .NET に実装されている場合、このようなサイクルは問題になりません。これは、.NET がガベージ コレクションであり、ガベージ コレクション アルゴリズムは、サイクル内でリンクされている場合でも、オブジェクトのグループを識別して再利用できるためです。

.NET とは異なり、C++ は参照カウントによってメモリを管理します。これは、オブジェクトのサイクルを検出して再利用することはできません。 この制限にもかかわらず、Win2D を使用する C++ アプリは、C++ イベント ハンドラーが既定でターゲット インスタンスへの強い参照ではなく弱い参照を保持するため、問題はありません。 そのため、ページはコントロールを参照し、コントロールはイベント ハンドラー デリゲートを参照しますが、このデリゲートはページを参照しないため、サイクルはありません。

この問題は、Win2D などの C++ WinRT コンポーネントが .NET アプリケーションで使用されている場合に発生します。

  • XAML ページはアプリケーションの一部であるため、ガベージ コレクションを使用します
  • Win2D コントロールは C++ で実装されているため、参照カウントを使用します
  • イベント ハンドラー デリゲートはアプリケーションの一部であるため、ガベージ コレクションを使用し、そのターゲット インスタンスへの厳密な参照を保持します

サイクルは存在しますが、このサイクルに参加している Win2D オブジェクトは .NET ガベージ コレクションを使用していません。 つまり、ガベージ コレクターはチェーン全体を表示できないため、オブジェクトを検出または再利用できません。 これが発生した場合、アプリケーションは明示的にサイクルを中断して支援する必要があります。 これを行うには、ページからコントロールへのすべての参照を解放する(上記の推奨に従う)か、コントロールからページを指す可能性のあるイベントハンドラーデリゲートへのすべての参照を解除します(ページの Unloaded イベントを使用してすべてのイベントハンドラーを解除します)。