다음을 통해 공유


메모리 누수 방지

관리되는 XAML 애플리케이션에서 Win2D 컨트롤을 사용하는 경우, 이러한 컨트롤이 가비지 수집기에서 회수되는 것을 방지할 수 있는 참조 수 주기를 피하도록 주의해야 합니다.

다음과 같은 경우 문제가 있습니다...

  • C#(네이티브 C++가 아닌)과 같은 .NET 언어에서 Win2D를 사용하는 경우
  • Win2D XAML 컨트롤 중 하나를 사용합니다.
  • Win2D 컨트롤의 이벤트를 구독합니다(예: Draw, , CreateResourcesSizeChanged...)
  • 앱이 두 개 이상의 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;
}

작업 예제는 예제 갤러리 데모 페이지를 참조하세요.

주기 누수를 테스트하는 방법

애플리케이션이 리카운트 주기를 올바르게 중단하는지 테스트하려면 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();

페이지로 이동한 다음 다른 페이지로 이동합니다. 모든 주기가 중단된 경우 1~2초 내에 Visual Studio 출력 창에 Debug.WriteLine 출력이 표시됩니다.

GC.Collect 호출 시 중단되고 성능이 저하되므로 누출 테스트를 마치는 즉시 이 테스트 코드를 제거해야 합니다.

폭력적인 세부 정보

주기는 객체 A가 B를 참조하는 동시에 B도 A를 참조하는 경우, 또는 A가 B를 참조하고 B가 C를 참조하는 동시에 C가 A를 참조하는 경우 등에서 발생합니다.

XAML 컨트롤의 이벤트를 구독하는 경우 이러한 종류의 주기는 거의 피할 수 없습니다.

  • XAML 페이지에는 해당 페이지에 포함된 모든 컨트롤에 대한 참조가 있습니다.
  • 컨트롤은 해당 이벤트를 구독한 처리기 대리자에 대한 참조를 보유합니다.
  • 각 대리자는 대상 인스턴스에 대한 참조를 보유합니다.
  • 이벤트 처리기는 일반적으로 XAML 페이지 클래스의 인스턴스 메서드이므로 대상 인스턴스 참조는 XAML 페이지를 다시 가리키며 주기를 생성합니다.

관련된 모든 개체가 .NET에서 구현되는 경우 .NET은 가비지 수집되므로 이러한 주기는 문제가 되지 않으며, 가비지 수집 알고리즘은 개체 그룹이 주기에 연결되어 있더라도 개체 그룹을 식별하고 회수할 수 있습니다.

.NET과 달리 C++는 참조 카운팅을 통해 메모리를 관리하므로 객체의 주기를 감지하고 회수할 수 없습니다. 이러한 제한에도 불구하고, C++ 이벤트 처리기는 기본적으로 대상 인스턴스에 대한 강력한 참조가 아닌 약한 참조를 보유하기 때문에 Win2D를 사용하는 C++ 앱에는 문제가 없습니다. 따라서 페이지는 컨트롤을 참조하고 컨트롤은 이벤트 처리기 대리자를 참조하지만 이 대리자는 페이지를 다시 참조하지 않으므로 주기가 없습니다.

문제는 Win2D와 같은 C++ WinRT 구성 요소가 .NET 애플리케이션에서 사용되는 경우에 발생합니다.

  • XAML 페이지는 애플리케이션의 일부이므로 가비지 수집을 사용합니다.
  • Win2D 컨트롤은 C++에서 구현되므로 참조 계산을 사용합니다.
  • 이벤트 처리기 대리자는 애플리케이션의 일부이므로 가비지 수집을 사용하고 대상 인스턴스에 대한 강력한 참조를 보유합니다.

주기가 있지만 이 주기에 참여하는 Win2D 개체는 .NET 가비지 수집을 사용하지 않습니다. 즉, 가비지 수집기가 전체 체인을 볼 수 없으므로 개체를 검색하거나 회수할 수 없습니다. 이 경우 애플리케이션은 주기를 명시적으로 중단하여 도움을 주어야 합니다. 이 작업은 페이지에서 컨트롤에 대한 모든 참조를 해제하거나(위에서 권장하는 대로) 컨트롤에서 페이지로 다시 가리킬 수 있는 이벤트 처리기 대리자로 모든 참조를 해제하여 수행할 수 있습니다(페이지 언로드된 이벤트를 사용하여 모든 이벤트 처리기 구독 취소).