マネージド XAML アプリケーションで Win2D コントロールを使用する場合は、ガベージ コレクターによってこれらのコントロールが再利用されるのを防ぐ可能性のある参照カウント サイクルを回避するように注意する必要があります。
〜であれば問題があります
- (ネイティブ C++ ではなく) C# などの .NET 言語から Win2D を使用している
- Win2D XAML コントロールのいずれかを使用します。
- Win2D コントロールのイベント (たとえば、
Draw
、CreateResources
、SizeChanged
など) をサブスクライブします。 - アプリが複数の 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 イベントを使用してすべてのイベントハンドラーを解除します)。
Windows developer