2015년 5월 30일 토요일

C# SynchronizationContext 사용 시 주의사항

C#에서 GUI를 구현하다보면 비즈니스 로직을 스레드로 구현하여 백그라운드에서 작동시키고 GUI는 현재의 상태를 출력하는 식의 구현을할 때가 있다. 스레드에서 GUI 쪽을 제어할 때 Cross-Thread로 인한 예외를 방지하기 위해서 SyncrhonizationContext를 사용하게 된다.
SyncrhonizationContext는 Send와 Post 메소드를 제공하는데 잘못 사용하면 GUI에 Deadlock이 발생할 수 있어서 사용상 주의가 필요하다.

SyncrhonizationContext의 Send와 Post 메소드의 차이는 해당 메소드가 리턴되었을 때 입력된 델리게이트의 수행 완료 여부이다. Send는 입력된 델리게이트가 모두 수행된 뒤 리턴되지만 Post는 델리게이트의 수행여부와 상관없이 리턴된다. 그런데 이는 SyncrhonizationContext와 Send나 Post를 호출한 Context간의 동기/비동기 수행 여부를 의미하는 것일 뿐 Send와 Post에 입력된 델리게이트 간의 동기/비동기 수행 여부를 의미하지 않는다. 일례로 호출된 Post의 델리게이트에서 Sleep이나 lock에 의해서 블록되어 있으면 비슷한 시간에 호출된 Send의 델리게이트는 계속 Post의 델리게이트가 종료될 때까지 기다리게 되므로 Deadlock이 발생할 수 있게된다.

Deadlock 예제:

void func1()
{
    lock(x)
    {
        SynchronizationContext.Send (  // <-- deadlock
            () =>
            {
                // ...
            }
            , null);
    }
}

void func2()
{
    SynchronizationContext.Post (
        () =>
        {
            lock(x) // <-- deadlock
            {
                // ...
            }
        }
        , null);
}

// func1, func2가 동시에 실행되었을 때 func1이 func2보다 약간이라도 빨리 수행되면 deadlock 발생 (func2가 약간이라도 먼저 수행되면(lock(x)에 먼저 도달하면) 문제 없음)
// 이 문제를 해결하려면 func2의 lock을 Post 메소드 바깥으로 빼거나 func1의 Send를 Post로 변경해야 함

따라서 SyncrhonizationContext의 Send와 Post는 lock으로 보호된 코드 처럼 한번에 한개씩만 호출됨을 명심하고 입력된 델리게이트 내에서 lock과 같은 동기화 관련 구문을 사용할 때에는 Deadlock을 유발하지 않을지 꼼꼼히 살펴보아야 한다.

댓글 없음:

댓글 쓰기