Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 5개 있습니다.)

WPF의 Window 객체를 생성했는데 GC 수집 대상이 안 되는 이유

질문이 하나 있군요.

안녕하세요 WPF 에서 Window객체가 가비지 콜렉션에 의해 수집되지 않는거 같아서 문의드립니다.
; https://www.sysnet.pe.kr/3/0/4896

실제로 WPF에서 다음과 같은 간단한 소스 코드로,

using System.Linq;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            foreach (int n in Enumerable.Range(1, 100000))
            {
                Window win = new Window();
            }
        }
    }
}

버튼 클릭 몇 번을 하면 OOM 예외가 발생하도록 만들 수 있습니다. 얼핏 보면 new Window만 했으니 GC 대상이 되어야 하지만 그렇지 않은 현상을 보이는 것입니다.

이런 경우 생각할 여지가 별로 없으니 다행입니다. 수행된 코드는 어차피 생성자 뿐이 없으니 그 안에서 아마도 다른 루트 객체에 자신의 참조를 추가했을 것입니다. 확인을 위해 .NET Reflector 등으로 소스 코드를 확인하면 답이 나오겠지요.

실제로 PresentationFramework 어셈블리의 System.Windows.Window 타입의 생성자를 찾아보면,

[SecurityCritical]
public Window()
{
    this._ownerHandle = IntPtr.Zero;
    this._updateHwndSize = true;
    this._updateHwndLocation = true;
    this._trackMaxWidthDeviceUnits = double.PositiveInfinity;
    this._trackMaxHeightDeviceUnits = double.PositiveInfinity;
    this._windowMaxWidthDeviceUnits = double.PositiveInfinity;
    this._windowMaxHeightDeviceUnits = double.PositiveInfinity;
    this._actualTop = double.NaN;
    this._actualLeft = double.NaN;
    this._dialogOwnerHandle = IntPtr.Zero;
    this._prePanningLocation = new Point(double.NaN, double.NaN);
    SecurityHelper.DemandUnmanagedCode();
    this._inTrustedSubWindow = false;
    this.Initialize();
}

다른 코드는 상관이 없으니 이젠 Initialize를 의심해 볼 수 있습니다.

private void Initialize()
{
    base.BypassLayoutPolicies = true;
    if (this.IsInsideApp)
    {
        if (Application.Current.Dispatcher.Thread == Dispatcher.CurrentDispatcher.Thread)
        {
            this.App.WindowsInternal.Add(this);
            if (this.App.MainWindow == null)
            {
                this.App.MainWindow = this;
            }
        }
        else
        {
            this.App.NonAppWindowsInternal.Add(this);
        }
    }
}

아하... 여기 있군요. WindowsInternal이든 NonAppWindowsInternal이든 자신을 추가하고 있습니다. 이렇게 this.App 객체가 참조를 잡아 두고 있으니 GC 해제가 안된 것입니다.

만약 참조 해제를 하고 싶다면 명시적으로 Close 메서드를 호출해 주면 됩니다.

foreach (int n in Enumerable.Range(1, 100000))
{
    Window win = new Window();
    win.Close();   
}

그런데, 조금 궁금한 부분이 있군요. 왜 WPF 개발팀은 Window 객체에 명시적으로 자원 해제를 위한 IDisposable 인터페이스를 구현하지 않았을까요?




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/19/2017]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2017-09-20 12시25분
[장진국] 친절한 답변 감사합니다^^ 덕분에 궁금중이 해결되었습니다.


[guest]
2017-09-20 02시12분
[이성환] 안녕하세요.

이미 본문에서 잘 설명이 되어 있지만 약간의 사족을 달아봅니다.

WPF 에서는 WindowBase 를 이용한 Window 생성 시 생성되는 모든 Window를
Application 클래스의 WindowsInternal 이라는 WindowsCollection 타입의 컬렉션 프로퍼티에 보관하도록 되어 있습니다.
(본문에 나오는 App 프로퍼티 역시 Window 클래스에서 Application.Current 형태로 보관하는 Application 객체입니다.)

따라서 Window 를 생성하면 Application의 WindowsInternal 에 추가되었다가 Close 되면 제거됩니다.
(본문에서 설명한 것과 같이 Show() 호출이 아니라 new 로 생성하여 Initialize() 가 호출되면 바로 등록됩니다. 그렇기 때문에 생성된 Window는 Close() 호출 전까지 Application에서 참조됩니다.)

Internal 이라는 접미로 알 수 있듯이 이 프로퍼티의 접근제한자는 internal 이며 이 프로퍼티를 public으로 노출하는 것이
Application 클래스의 Windows 라는 프로퍼티 입니다.
이 Windows 프로퍼티는 WindowsInternal을 clone 하여 반환하긴 하지만 Deep copy 가 아니라 컬렉션 객체를 옮겨 담는 형태로 copy됩니다.

사용자는 public으로 노출된 Windows 프로퍼티에 접근해 현재 생성된 Window 들의 정보를 사용할 수 있습니다. (WindowsManager 같은 것을 만들 때 유용합니다.)
이 때 가장 먼저 만들어지는 Window 객체를 Application 클래스의 MainWindow라는 별도의 프로퍼티에 따로 할당하여 사용하는데
이 MainWindow 는 특별한 타입이 아니라 그냥 Window 타입이며 ShutdownMode 같은 영역에 기준으로 사용되기도 합니다.
(MainWindow는 수동으로 재할당이 가능합니다.)

WPF의 Window 관리를 인지하지 못하고 Window 를 생성하여 처리하면 메모리 누수로 이어질 가능성이 높기 때문에 주의가 필요합니다.

여기까지 알아두면 그럭저럭 쓸모있는 사족이었습니다.
[guest]
2017-09-20 09시12분
좋은 내용의 덧글 감사드립니다. ^^
정성태

... 61  62  63  64  65  66  67  68  69  70  71  [72]  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
11855정성태3/25/201914588개발 환경 구성: 436. 페이스북 HTTPS 인증을 localhost에서 테스트하는 방법
11854정성태3/25/201910481VS.NET IDE: 133. IIS Express로 호스팅하는 사이트를 https로 접근하는 방법
11853정성태3/24/201912677개발 환경 구성: 435. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면? - 두 번째 이야기 [1]
11852정성태3/20/201912886개발 환경 구성: 434. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면?파일 다운로드1
11851정성태3/19/201915914Linux: 8. C# - 리눅스 환경에서 DllImport 대신 라이브러리 동적 로드 처리 [2]
11850정성태3/18/201914329.NET Framework: 813. C# async 메서드에서 out/ref/in 유형의 인자를 사용하지 못하는 이유
11849정성태3/18/201914246.NET Framework: 812. pscp.exe 기능을 C#으로 제어하는 방법파일 다운로드1
11848정성태3/17/201911478스크립트: 14. 윈도우 CMD - 파일이 변경된 경우 파일명을 변경해 복사하고 싶다면?
11847정성태3/17/201915449Linux: 7. 리눅스 C/C++ - 공유 라이브러리 동적 로딩 후 export 함수 사용 방법파일 다운로드1
11846정성태3/15/201913847Linux: 6. getenv, setenv가 언어/운영체제마다 호환이 안 되는 문제
11845정성태3/15/201914600Linux: 5. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) [3]
11844정성태3/14/201915498개발 환경 구성: 434. Visual Studio 2019 - 리눅스 프로젝트를 이용한 공유/실행(so/out) 프로그램 개발 환경 설정 [1]파일 다운로드1
11843정성태3/14/201911195기타: 75. MSDN 웹 사이트를 기본으로 영문 페이지로 열고 싶다면?
11842정성태3/13/201910584개발 환경 구성: 433. 마이크로소프트의 CoreCLR 프로파일러 예제를 Visual Studio CMake로 빌드하는 방법 [1]파일 다운로드1
11841정성태3/13/201910588VS.NET IDE: 132. Visual Studio 2019 - CMake의 컴파일러를 기본 g++에서 clang++로 변경
11840정성태3/13/201911611오류 유형: 526. 윈도우 10 Ubuntu App 환경에서는 USB 외장 하드 접근 불가
11839정성태3/12/201914324디버깅 기술: 124. .NET Core 웹 앱을 호스팅하는 Azure App Services의 프로세스 메모리 덤프 및 windbg 분석 개요 [3]
11838정성태3/7/201917180.NET Framework: 811. (번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes [1]파일 다운로드1
11837정성태3/6/201927077기타: 74. 도서: 시작하세요! C# 7.3 프로그래밍 [10]
11836정성태3/5/201914771오류 유형: 525. Visual Studio 2019 Preview 4/RC - C# 8.0 Missing compiler required member 'System.Range..ctor' [1]
11835정성태3/5/201914516.NET Framework: 810. C# 8.0의 Index/Range 연산자를 .NET Framework에서 사용하는 방법 및 비동기 스트림의 컴파일 방법 [3]파일 다운로드1
11834정성태3/4/201913408개발 환경 구성: 432. Visual Studio 없이 최신 C# (8.0) 컴파일러를 사용하는 방법
11833정성태3/4/201914131개발 환경 구성: 431. Visual Studio 2019 - CMake를 이용한 공유/실행(so/out) 리눅스 프로젝트 설정파일 다운로드1
11832정성태3/4/201911113오류 유형: 524. Visual Studio CMake - rsync: connection unexpectedly closed
11831정성태3/4/201910657오류 유형: 523. Visual Studio 2019 - 새 창으로 뜬 윈도우를 닫을 때 비정상 종료
11830정성태2/26/201910474오류 유형: 522. 이벤트 로그 - Error opening event log file State. Log will not be processed. Return code from OpenEventLog is 87.
... 61  62  63  64  65  66  67  68  69  70  71  [72]  73  74  75  ...