💣 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 메모리 누수 해결
- 서버 안정성 향상
- 프레임워크 내부 동작 이해
'C#' 카테고리의 다른 글
| DI 컨테이너 직접 구현하기 - Reflection 종합편 (0) | 2026.02.16 |
|---|---|
| Delegate vs Interface - 설계 관점에서의 선택 기준 (0) | 2026.02.12 |
| C# Reflection 성능 최적화 - Expression Tree와 IL Emit (0) | 2026.02.10 |
| C# 비동기 / 병렬 처리 + GC 최적화 실전 가이드 (0) | 2026.02.08 |
| C# 개발자를 위한 고급 메모리 관리: GC를 넘어서 Span과 Memory까지 (0) | 2025.12.14 |