방프리

24.04.14 Chapter4. 병렬 처리 (Item 39) 본문

C#/More Effective C#

24.04.14 Chapter4. 병렬 처리 (Item 39)

방프리 2024. 4. 14. 23:00

Item 39 : XAML 환경에서 스레드 간 호출을 이해하라

이벤트 핸들러를 좀 더 쉽게 작성하고자 할 때 제네릭을 활용하여 다음과 같이 작성해볼 수 있다.

//Background 스레드 사용 시
public static class XAMLControlExtensions
{
    public static void InvokeIfNeeded(
        this System.Windows.threading.DispatcherObject ctl,
        Action doit,
        system.Windows.Threading.DispatcherPriority priority)
    {
        if (System.Threading.Thread.CurrentThread != ctl.Dispatcher.Thread)
        {
            ctl.Dispatcher.Invoke(priority, doit);
        }
        else
        {
            doit();
        }
    }
    
public static void InvokeIfNeeded<T>(
        this System.Windows.Threading.DispatcherObject ctl,
        Action<T> doit,
        T args,
        System.Windows.Threading.DispatcherPriority priority)
    {
        if (System.Threading.Thread.CurrentThread != ctl.Dispatcher.Thread)
        {
            ctl.Dispatcher.Invoke(priority, doit);
        }
        else
        {
            doit();
        }
    }
}

//윈도우 폼 컨트롤러용
public static class ControlExtensions
{
    public static void InvokeIfNeeded(this Control ctl, Action doit,
        system.Windows.Threading.DispatcherPriority priority)
    {
        if (ctl.IsHandleCreated == false)
        {
            doit();
        }
        else if (ctl.InvokeRequired)
        {
            ctl.Invoke(doit);
        }
        else
        {
            doit();
        }
    }
    
public static void InvokeIfNeeded<T>(this Control ctl, 
    	Action<T> doit, T args)
    {
        if (ctl.IsHandleCreated == false)
        {
            throw new InvalidOperationException("Window Handle for ctl has not been created");
        }
        else if (ctl.InvokeRequired)
        {
            ctl.Invoke(doit);
        }
        else
        {
            doit();
        }
    }
}

//사용 시
private void OnTick(object sender, EventArgs e)
{
    this.InvokeAsync(() => toolStripStatusLabel1.Text =
         DateTime.Now.ToLongTimeString());
}

컨트롤이 생성된 후라면 InvokeRequired는 충분히 빠르며 항상 안전하게 동작한다. 하지만 대상 컨트롤이 아직 생성되지 않으면 적절한 윈도우를 찾기 위해서 훨씬 많은 시간을 소비하게 된다. InvokeRequired도 비용이 들지만 매번 Control.Invoke()를 호출하는 것보단 장점이 많다.

Control.Invoke()는 마셜링된 델리게이트 호출 작업이 완료될 때까지 백그라운드 스레드를 정지시킨다. 그래서 멀티스레드 환경임에도 동기적으로 동작하는 것처럼 보인다. 델리게이트는 항상 방어적으로 코딩해야한다. 스레드 사이의 마셜링 과정에서 예외들이 묻혀 버릴 수 있기 때문이다.

WndProc()가 Invoke 메시지를 받으면 WndProc()는 큐에 있는 모든 델리게이트를 처리한다. 요청작업이 순서를 시켜서 처리되길 바라고 Invoke()와 BeginInvoke()를 혼용해야 하는 상황이라면 타이밍 문제가 발생할 수 있다.

Invoke()와 InvokeRequired는 내부적으로 꽤 많은 작업을 수행한다. 이 모든 작업이 필요한 이유는 윈도우 폼 컨트롤이 싱글스레드 아파트 모델로 만들어졌기 때문이다.

Comments