프로그래밍/WPF

[C#/WPF] SynchronizationContext와 UI 스레드 동기화

흔한티벳여우 2025. 2. 10. 12:18
반응형

1. SynchronizationContext란?

SynchronizationContext는 코드 실행 컨텍스트를 추상화한 클래스입니다. 주로 스레드 간 작업을 조율할 때 사용되며, 특히 UI 스레드와 작업 스레드 간의 통신에서 매우 유용합니다.

public class SynchronizationContext
{
    public virtual void Post(SendOrPostCallback d, object state);
    public virtual void Send(SendOrPostCallback d, object state);
}

 

2. 왜 사용해야 하는가?

2.1 기존 Dispatcher.Invoke 방식의 문제점

// UI 스레드에서 실행되어야 하는 코드
Application.Current.Dispatcher.Invoke(() => 
{
    LastLog = "처리 완료";
    Progress = 100;
});

- 명시적인 Dispatcher 호출이 필요

  • WPF에 종속적인 코드
  • 블로킹 방식으로 동작하여 성능 저하 가능
  • 코드가 지저분해짐

2.2 SynchronizationContext의 장점

private readonly SynchronizationContext _uiContext;

public DeepLearningBase()
{
    _uiContext = SynchronizationContext.Current; // UI 스레드의 컨텍스트 캡처
}

protected void UpdateUI()
{
    _uiContext?.Post(_ => 
    {
        LastLog = "처리 완료";
        Progress = 100;
    }, null);
}

 

- 플랫폼 독립적 (WPF, WinForms, ASP.NET 등)

  • 비동기적 실행으로 성능 향상
  • 더 깔끔한 코드
  • 테스트 용이성 향상

3. 기본 사용법

3.1 UI 컨텍스트 캡처

public class MyViewModel
{
    private readonly SynchronizationContext _uiContext;

    public MyViewModel()
    {
        // 반드시 UI 스레드에서 생성자 호출
        _uiContext = SynchronizationContext.Current;
    }
}

 

3.2 Post vs Send

// 비동기 실행 (권장)
_uiContext.Post(_ => UpdateUI(), null);

// 동기 실행 (블로킹)
_uiContext.Send(_ => UpdateUI(), null);

 

3.3 이벤트 처리 예제

private void ProcessEvents()
{
    foreach (var args in _eventQueue.GetConsumingEnumerable())
    {
        _uiContext?.Post(_ => 
        {
            switch (args)
            {
                case StatusUpdateEventArgs status:
                    StatusUpdated?.Invoke(this, status);
                    break;
                case ProgressUpdateEventArgs progress:
                    ProgressUpdated?.Invoke(this, progress);
                    break;
            }
        }, null);
    }
}

 

4. 사용하면 좋은 상황

4.1 장시간 실행되는 작업

public async Task LongRunningOperation()
{
    await Task.Run(() => 
    {
        // 무거운 작업 수행
        for (int i = 0; i < 100; i++)
        {
            // 진행 상황 UI 업데이트
            _uiContext?.Post(_ => 
            {
                Progress = i;
                LastLog = $"처리중... {i}%";
            }, null);
        }
    });
}

 

4.2 이벤트 기반 시스템

public class EventProcessor
{
    private readonly SynchronizationContext _uiContext;
    
    public void OnDataReceived(object sender, DataEventArgs e)
    {
        // 데이터 처리는 백그라운드에서
        Task.Run(() => 
        {
            var result = ProcessData(e.Data);
            
            // UI 업데이트는 UI 스레드에서
            _uiContext?.Post(_ => 
            {
                UpdateUIWithResult(result);
            }, null);
        });
    }
}

 

5. 주의 사항

1. 컨텍스트 캡처 시점

  • 반드시 UI 스레드에서 SynchronizationCentext.Current를 캡처해야함
  • 작업 스레드에서 캡처하면 null이 될 수 있음

2. 메모리 누수 방지

   public void Dispose()
   {
       _uiContext = null; // 참조 해제
   }

3. 성능 고려

Post는 비동기적으로 동작하므로 Send보다 선호

너무 잦은 UI 업데이트는 피해야함

 

결론

SynchronizationContext는 UI 스레드와 작업 스레드 간의 통신을 추상화하고 단순화합니다. 특히 이벤트 기반 시스템이나 장시간 실행되는 작업에서 UI 업데이트를 처리할 때 매우 유용합니다. Dispatcher.Invoke보다 더 유연하고 플랫폼 독립적인 방식을 제공하며, 코드의 가독성과 유지보수성을 향상시킵니다.

반응형