앞선 글에 이어 SynchronizationContext 에 관해서 좀더 이야기 해 보자.
이제까지 코드 혹은 객체와 Thread를 커플링 시키는 방식으로 동기화를 구현하는 것에 관해서 이야기 했는데, 여기서 한가지 필수적인 메커니즘을 빼먹은 것 같다.
Thread에 종속된 객체는 그 Thread에서만 접근할 수 있고, 따라서 그 객체에 접근하기 위해서는 SynchronizationContext 를 사용해야 한다고 했다. 따라서 SynchronizationContext 는 그 Thread에게, 이 delegate를 실행시켜 달라고 요청하는 도구라고 볼 수 있다.
그렇다면 결국 그 객체가 종속된 Thread는, 요청을 받아 들이고 처리하는 메커니즘을 구현하고 있어야 한다.
여러가지 방법이 있을 수 있겠지만 윈도우 운영체제에서는 대부분 메세지 기반(Message Driven), 메세지 펌프로 구현된다.
앞선 글에서도 말했지만 객체와 Thread의 종속은 대부분 GUI와 관련된 경우가 많고, 윈도우 운영체제에서는 GUI와 관련해서 거의 항상 메세지 펌프가 작동하기 때문에, 이를 이용한 SynchronizationContext 는 궁합이 잘 맞는다고 할 수 있다.
이를 위해 WinForm도 그렇고 WPF에서도 실행 요청을 위한 메세지를 하나 정의(RegisterWindowMessage)해서 사용하고 있다.
다시 본론으로 돌아와서,
닷넷에서 볼 수 있는 SynchronizationContext 는 3가지가 있다.
System.Threading.SynchronizationContext
|
+- System.Windows.Forms.WindowsFormsSynchronizationContext
|
+- System.Windows.Threading.DispatcherSynchronizationContext
일단 SynchronizationContext 는 다른 2개의 부모 클래스인데,
MSDN의 설명으로나, Reflector 를 이용해서 소스를 보더라도, SynchronizationContext 클래스는 실제로 동기화를 수행하지 않는다.
이 SynchronizationContext 의 Post를 호출하면 그냥 ThreadPool을 이용하여 delegate를 실행해 버린다. 당연히 위에서 설명한 동기화 처리가 될리가 없다.(더군다나 SynchronizationContext는 Abstract 클래스도 아니라서 그냥 생성해서 사용할 수도 있다.)
이쯤되면 SynchronizationContext의 존재이유에 대해서 의문이 생기기도 하는데,
닷넷에서 모든 Thread는 자신을 위한 SynchronizationContext를 가지고 있다.
하지만 딱히 동기화를 해야 하는 객체를 소유하지 않는 일반 Thread인 경우에는 동기화를 구현할 필요가 없다. 이런 Thread는 SynchronizationContext를 가지게 된다. 뭐 디폴트 SynchronizationContext라고 생각할 수 있다.
'딱히 동기화를 할 필요없는 경우에 사용하는 동기화 도구' 라고 할 수 있는 좀 아이러니 한 상황을 위해서 말이다.
앞서, 특별하게 동기화를 통한 보호를 받아야 하는 경우는 GUI 관련 객체들이고, 닷넷에는 2개의 GUI 프레임워크가 있는데, WinForm 과 WPF 라고 이야기 했다.
각각을 위한 SynchronizationContext 가 WindowsFormsSynchronizationContext 와 DispatcherSynchronizationContext 다.
위에서 이야기 한 것 중에, 모든 Thread는 SynchronizationContext를 가진다고 했고, 디폴트로는 동기화 기능이 없는 SynchronizationContext가 사용된다고 했다. 그럼 WindowsFormsSynchronizationContext 나 DispatcherSynchronizationContext 는 언제, 어떻게 사용되는 것일까?
Thread를 생성할 때, 이 Thread는 WinForm GUI를 위한 Thread, 이 Thread는 WPF GUI를 위한 Thread라고 따로 생성하는 건 아니다.
또한 일반적인 용도로 생성된 Thread가 GUI 용 Thread로 바뀌는 경우도 있을 수 있다.
힌트는 서두에 밝힌 실행요청 메커니즘에 있다. 객체를 소유한 Thread가 실행 요청을 받기 위해서는 메세지 루프를 가져야 한다는 사실 말이다.
WinForm도 그렇고 WPF 에서도 그렇고,
WinForm용 메세지 루프로 진입하는 부분, 혹은 WPF 용 메세지 루프(Dispatcher.PushFrame)로 진입하는 부분에서, 현재 루프를 실행하는 CurrentThread에 대해, WindowsFormsSynchronizationContext 혹은 DispatcherSynchronizationContext 를 세팅하도록 되어 있다.
메세지 루프로 진입한다는 이야기는 이제부터 실행요청을 받아 들일 수 있는 상태가 되기 때문이다.
그렇기 때문에 메세지 루프를 빠져나올 때는 CurrentThread에 대해 이전 SynchronizationContext를 복구하도록 되어 있다. 메세지 루프를 빠져 나온다는 것은 더이상 실행요청을 받아들이지 못한다는 의미가 되기 때문이다.
그리고 DispatcherSynchronizationContext 는 Dispatcher.Invoke를 이용하여 구현되어 있다.
WPF에는 이미 객체와 Thread의 종속관계를 명확히 하면서, 다른 Thread에서 객체를 접근하는 방법으로 이미 Dispatcher의 Invoke를 마련해 놓았다. 이건 SynchronizationContext가 추구하는 동기화와 정확하게 일치한다.
따라서 DispatcherSynchronizationContext 가 Dispatcher.Invoke를 이용하여 구현되는건 필연적이다.
[출처] Synchronization Context 가 추구하는 동기화2|작성자 쫌조
'Programming > C#' 카테고리의 다른 글
Random Class (0) | 2015.02.08 |
---|---|
SynchronizationContext 가 추구하는 동기화 #1 (0) | 2015.02.07 |
AppDomain 프로그래밍에 대한 이야기 (0) | 2015.02.05 |
How to: Determine Which .NET Framework Versions Are Installed (0) | 2015.02.05 |
Lazy Initialization (0) | 2015.02.05 |