본문 바로가기

C#

Event 메모리 누수와 WeakEvent 패턴 완전 분석

 

💣 C# Event 메모리 누수 & WeakEvent 패턴

C#에서 Event는 매우 안전해 보인다. 하지만 실제로는 GC 관점에서 가장 위험한 기능 중 하나다.

특히 다음 환경에서는 치명적이다.

  • WPF / WinForms
  • 장시간 실행되는 서버
  • 플러그인 구조
  • Observer 패턴

1️⃣ Event 메모리 누수란?

Event 메모리 누수는 GC가 객체를 수거하지 못하는 상태를 의미한다.

원인은 단 하나다.

Event는 내부적으로 Delegate 리스트를 보관하며, 구독자 객체를 강하게 참조한다.

2️⃣ 문제의 근본 원리 (GC 관점)

다음 구조를 보자.


Publisher ───► Event ───► Subscriber

Publisher가 살아 있는 한, Subscriber는 GC 대상이 될 수 없다.

GC Root 기준

  • Static 필드
  • Thread
  • Native 핸들

Publisher가 GC Root에 연결되어 있으면, Event 체인 전체가 살아남는다.

📘 Microsoft 공식 문서: .NET 가비지 컬렉션 개요

 

.NET 가비지 수집 - .NET

.NET의 가비지 수집에 대해 알아봅니다. .NET 가비지 수집기는 애플리케이션에 대한 메모리 할당 및 릴리스를 관리합니다.

learn.microsoft.com

 


3️⃣ 실제 메모리 누수 발생 코드


class Publisher
{
    public event Action SomethingHappened;
}

class Subscriber
{
    public Subscriber(Publisher pub)
    {
        pub.SomethingHappened += OnEvent;
    }

    void OnEvent()
    {
        Console.WriteLine("이벤트 수신");
    }
}

Subscriber 인스턴스를 더 이상 사용하지 않아도 GC는 이를 제거하지 못한다.

이유: Publisher가 Event를 통해 Subscriber를 참조 중이기 때문


4️⃣ 가장 흔한 실무 사고 사례

❌ UI 화면 전환 시 메모리 증가

  • View 생성
  • Event 구독
  • 화면 닫기
  • GC 후에도 메모리 유지 ❗

❌ 서버에서 Handler 누적

  • 요청마다 이벤트 등록
  • 해제 누락
  • 메모리 점진적 증가

5️⃣ 전통적 해결 방법: 수동 해제


pub.SomethingHappened -= OnEvent;

하지만 이 방식은:

  • 실수하기 쉽고
  • 예외 발생 시 누락 가능
  • 대규모 구조에서 관리 불가

6️⃣ WeakEvent 패턴이란?

WeakEvent 패턴은 약한 참조(WeakReference)를 사용하여 GC 수거를 방해하지 않는 이벤트 구조다.

📘 Microsoft 공식 문서: Weak Event 패턴 (WPF)

 

약한 이벤트 패턴 - WPF

WPF(Windows Presentation Foundation)에서 약한 이벤트 패턴을 사용하여 메모리 누수를 방지하는 방법을 알아봅니다.

learn.microsoft.com

 


7️⃣ WeakReference 기반 구현


class WeakEventHandler
{
    private readonly WeakReference _target;
    private readonly MethodInfo _method;

    public WeakEventHandler(Delegate handler)
    {
        _target = new WeakReference(handler.Target);
        _method = handler.Method;
    }

    public bool Invoke()
    {
        if (_target.Target != null)
        {
            _method.Invoke(_target.Target, null);
            return true;
        }
        return false;
    }
}

GC가 Subscriber를 수거하면, WeakReference는 자동으로 끊어진다.


8️⃣ WeakEventSource 예제


class WeakEventSource
{
    private readonly List _handlers = new();

    public void Subscribe(Action handler)
    {
        _handlers.Add(new WeakEventHandler(handler));
    }

    public void Raise()
    {
        _handlers.RemoveAll(h => !h.Invoke());
    }
}

이 구조는:

  • 명시적 해제 불필요
  • GC 친화적
  • 장기 실행 시스템에 적합

9️⃣ WPF WeakEventManager

WPF는 WeakEvent 문제를 너무 많이 겪어서 아예 전용 매니저를 제공한다.


WeakEventManager<Button, RoutedEventArgs>
    .AddHandler(button, "Click", OnClick);

📘 Microsoft 공식 문서: WeakEventManager 클래스

 

WeakEventManager 클래스 (System.Windows)

약한 이벤트 패턴에서 사용되는 이벤트 관리자에 대해 기본 클래스를 제공합니다. 이 관리자는 마찬가지로 패턴을 사용하는 이벤트(콜백)에 대해 수신기를 추가하거나 제거합니다.

learn.microsoft.com

 


🔟 언제 WeakEvent를 써야 하는가?

상황 권장 방식
단기 객체 일반 Event
장기 Publisher WeakEvent
UI 요소 WeakEvent
서버 프로세스 WeakEvent

11️⃣ 정리

  • Event는 안전하지 않다
  • GC는 Delegate 체인을 수거하지 못한다
  • WeakEvent는 선택이 아니라 필수인 경우가 많다

이 개념을 이해하면?

  • UI 메모리 누수 해결
  • 서버 안정성 향상
  • 프레임워크 내부 동작 이해
반응형