Поделиться через


Предотвращение утечки памяти

При использовании элементов управления Win2D в управляемых приложениях XAML необходимо принять меры, чтобы избежать циклов подсчета ссылок, которые могут предотвратить восстановление этих элементов сборщиком мусора.

У вас есть проблема, если...

  • Вы используете Win2D на языке .NET, например C# (не собственный C++)
  • Вы используете один из элементов управления XAML Win2D:
  • Вы подписываетесь на события элемента управления Win2D (например Draw, , CreateResources... SizeChanged)
  • Приложение перемещается назад и вперед между несколькими страницами XAML

Если выполнены все эти условия, цикл счетчика ссылок будет держать элемент управления Win2D от когда-либо собираемого мусора. Новые ресурсы Win2D выделяются при каждом переходе приложения на другую страницу, но старые ресурсы никогда не освобождаются так, чтобы утечка памяти была утечка. Чтобы избежать этого, необходимо добавить код для явного разрыва цикла.

Исправление

Чтобы разорвать цикл подсчета ссылок и позволить вашей странице собирать мусор:

  • Перехватчик Unloaded события страницы XAML, содержащей элемент управления Win2D
  • В обработчике Unloaded вызовите RemoveFromVisualTree элемент управления Win2D
  • В обработчике отпустите (по параметру ) nullвсе явные Unloaded ссылки на элемент управления Win2D

Пример кода:

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

Примеры работы см. на любой из демонстрационных страниц коллекции примеров.

Как протестировать утечки цикла

Чтобы проверить правильность нарушения циклов ссылок приложения, добавьте метод завершения на все страницы XAML, содержащие элементы управления Win2D:

~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();

Перейдите к странице, а затем вдали от нее на другую страницу. Если все циклы были нарушены, вы увидите Debug.WriteLine выходные данные в области выходных данных Visual Studio во второй или двух.

Обратите внимание, что вызов GC.Collect нарушает производительность, поэтому вы должны удалить этот тестовый код сразу после завершения тестирования на утечки!

Подробности гори

Цикл возникает, когда объект A имеет ссылку на B, в то же время, что и ссылка на A. Или когда A ссылается на B и B ссылки на C, а C ссылается на A и т. д.

При подписке на события элемента управления XAML этот цикл является довольно неизбежным:

  • Страница XAML содержит ссылки на все элементы управления, содержащиеся в нем
  • Элементы управления содержат ссылки на делегаты обработчика, которые были подписаны на события.
  • Каждый делегат содержит ссылку на целевой экземпляр.
  • Обработчики событий обычно являются методами экземпляров класса страниц XAML, поэтому их целевые экземпляры ссылаются на страницу XAML, создавая цикл

Если все участвующие объекты реализованы в .NET, такие циклы не являются проблемой, так как .NET собирается мусор, а алгоритм сборки мусора может определить и восстановить группы объектов, даже если они связаны в цикле.

В отличие от .NET, C++ управляет памятью путем подсчета ссылок, который не может обнаруживать и освободить циклы объектов. Несмотря на это ограничение, приложения C++ с помощью Win2D не имеют проблем, так как обработчики событий C++ по умолчанию используют слабые, а не сильные ссылки на их целевой экземпляр. Поэтому страница ссылается на элемент управления, а элемент управления ссылается на делегат обработчика событий, но этот делегат не ссылается на страницу, поэтому цикл отсутствует.

Проблема возникает, когда компонент WinRT C++, например Win2D, используется приложением .NET:

  • Страница XAML является частью приложения, поэтому использует сборку мусора
  • Элемент управления Win2D реализован в C++, поэтому использует подсчет ссылок
  • Делегат обработчика событий является частью приложения, поэтому использует сборку мусора и содержит надежную ссылку на целевой экземпляр.

Существует цикл, но объекты Win2D, участвующие в этом цикле, не используют сборку мусора .NET. Это означает, что сборщик мусора не может видеть всю цепочку, поэтому он не может обнаруживать или удалять объекты. Когда это происходит, приложение должно помочь, явно разорвая цикл. Это можно сделать либо путем выпуска всех ссылок со страницы на элемент управления (как рекомендуется выше), либо путем освобождения всех ссылок из элемента управления на делегаты обработчика событий, которые могут указывать на страницу (с помощью события выгрузки страницы для отмены подписки всех обработчиков событий).