Udostępnij za pośrednictwem


Unikanie przecieków pamięci

W przypadku korzystania z kontrolek Win2D w zarządzanych aplikacjach XAML należy zachować ostrożność, aby uniknąć cykli referencji, które mogą uniemożliwić odzyskiwanie tych kontrolek przez śmieciarza pamięci.

Masz problem, jeśli...

Jeśli zostaną spełnione wszystkie te warunki, cykl liczników referencji uniemożliwi usunięcie kontrolki Win2D przez mechanizm czyszczący pamięć. Nowe zasoby Win2D są przydzielane za każdym razem, gdy aplikacja przechodzi do innej strony, ale stare nigdy nie są zwalniane, co prowadzi do wycieku pamięci. Aby tego uniknąć, należy dodać kod, aby jawnie przerwać cykl.

Jak rozwiązać problem

Aby przerwać cykl liczby odwołań i pozwolić stronie na odzyskiwanie pamięci:

  • Podłącz zdarzenie Unloaded do strony XAML, która zawiera kontrolkę Win2D
  • W procedurze obsługi Unloaded wywołaj RemoveFromVisualTree na kontrolce Win2D
  • W obsłudze Unloaded, zwolnij (przez ustawienie na null) wszelkie jawne odwołania do kontrolki Win2D.

Przykładowy kod:

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

Aby zapoznać się z przykładami roboczymi, zobacz dowolną stronę demonstracyjną z Galerii Przykładów.

Jak przetestować przecieki cyklu

Aby sprawdzić, czy aplikacja prawidłowo przerywa cykle refcount, dodaj metodę finalizatora do wszystkich stron XAML zawierających kontrolki Win2D:

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

W konstruktorze App skonfiguruj czasomierz, aby zbieranie śmieci odbywało się w regularnych odstępach czasu.

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

Przejdź do strony, a następnie opuść ją, przechodząc na inną stronę. Jeśli wszystkie cykle zostały przerwane, zobaczysz dane wyjściowe Debug.WriteLine w okienku wyjściowym programu Visual Studio w ciągu jednej lub dwóch sekund.

Należy pamiętać, że wywołanie GC.Collect jest zakłócające i negatywnie wpływa na wydajność, dlatego należy usunąć ten kod testowy zaraz po zakończeniu wykrywania wycieków!

Gory szczegóły

Cykl występuje, gdy obiekt A ma odwołanie do B, w tym samym czasie, co B ma również odwołanie do A. Lub gdy A odwołuje się do B i B odwołuje się do C, podczas gdy C odwołuje się do A itp.

W przypadku subskrybowania zdarzeń kontrolki XAML ten rodzaj cyklu jest prawie nieunikniony:

  • Strona XAML zawiera odwołania do wszystkich zawartych w nim kontrolek
  • Elementy sterujące przechowują odwołania do delegatów obsługi, które zostały przypisane do ich zdarzeń.
  • Każdy delegat przechowuje odwołanie do docelowego obiektu
  • Procedury obsługi zdarzeń to zazwyczaj metody instancyjne klasy strony XAML, więc ich instancje docelowe odnoszą się z powrotem do strony XAML, tworząc cykl.

Jeśli wszystkie zaangażowane obiekty są implementowane na platformie .NET, takie cykle nie są problemem, ponieważ platforma .NET ma mechanizm zbierania śmieci, a algorytm zarządzania pamięcią jest w stanie identyfikować i usuwać grupy obiektów z pamięci, nawet jeśli są one połączone w cyklu.

W przeciwieństwie do platformy .NET język C++ zarządza pamięcią przez zliczanie odwołań, co nie może wykryć i odzyskać cykli obiektów. Pomimo tego ograniczenia aplikacje języka C++ korzystające z win2D nie mają problemu, ponieważ programy obsługi zdarzeń języka C++ domyślnie przechowują słabe, a nie silne odwołania do ich wystąpienia docelowego. W związku z tym strona odwołuje się do kontrolki, a kontrolka odwołuje się do delegata programu obsługi zdarzeń, ale ten delegat nie odwołuje się z powrotem do strony, więc nie ma cyklu.

Problem występuje, gdy składnik WinRT języka C++, taki jak Win2D, jest używany przez aplikację platformy .NET:

  • Strona XAML jest częścią aplikacji, dlatego używa mechanizmu zbierania śmieci.
  • Kontrolka Win2D jest implementowana w języku C++, dlatego używa zliczania odwołań
  • Delegat obsługi zdarzeń jest częścią aplikacji, dlatego używa odśmiecania pamięci i przechowuje silne odwołanie do swojego docelowego wystąpienia.

Istnieje cykl, ale obiekty Win2D uczestniczące w tym cyklu nie korzystają ze zbierania pamięci .NET. Oznacza to, że moduł odśmieceń pamięci nie może zobaczyć całego łańcucha, więc nie może wykryć ani odzyskać obiektów. W takim przypadku aplikacja musi pomóc, jawnie przerywając cykl. Można to zrobić, zwalniając wszystkie odwołania ze strony do kontrolki (zgodnie z zaleceniami powyżej) lub zwalniając wszystkie odwołania z kontrolki do delegatów programu obsługi zdarzeń, które mogą wskazywać z powrotem na stronę (przy użyciu strony Zwolniono zdarzenie, aby anulować subskrypcję wszystkich procedur obsługi zdarzeń).