My friends, my life, my style - James S.F. Hsieh

11/06/2009

Thread, SynchronizationContext and Dispatcher for .NET




Dispatcher 的 Constructor 是 private 的, 所以我們只幾種辦法得到 Dispatcher, 那就是調用 Dispatcher.CurrentDispatcher 這個 static property 或是 Dispatcher.FromThread 這個 static function, MSDN 有描述這個 property 的語意 "Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread." 我們來看看相關的實作, 該 property 會調用 Dispatcher.FromThread 去找尋當下這個 thread 對應的 dispatcher.

public static Dispatcher CurrentDispatcher
{
    get
    {
        Dispatcher dispatcher = FromThread(Thread.CurrentThread);
        if (dispatcher == null) // 找不到就建立一個新的
        {
            dispatcher = new Dispatcher(); 
            // Constructor 會把自己加到 Dispatcher._dispatchers 中
        }
        return dispatcher;
    }
}
而 Dispatcher.FromThread 則會去Dispatcher._dispatchers 這個 List 找尋相對應的 dispatcher, 所以, dispatcher 跟 Thread 是有對應關係的.
public static Dispatcher FromThread(Thread thread)
{
    lock (_globalLock)
    {
        Dispatcher target = null;
        if (thread != null)
        {
            target = _possibleDispatcher.Target as Dispatcher; 
            // 檢查前一次找到的是否符合
            if ((target == null) || (target.Thread != thread))
            {
                target = null;
                for (int i = 0; i < _dispatchers.Count; i++)
                {
                    Dispatcher dispatcher2 = 
                        _dispatchers[i].Target as Dispatcher;
                    if (dispatcher2 != null)
                    {
                        if (dispatcher2.Thread == thread)
                        {
                            target = dispatcher2;
                        }
                    }
                    else
                    {
                        _dispatchers.RemoveAt(i); // 已經被回收掉了就從 list 中移除
                        i--;
                    }
                }
                if (target != null)
                {
                    _possibleDispatcher.Target = target; // 快取前一次找到的
                }
            }
        }
        return target;
    }
} 


接下來我們來看看 Dispatcher 如何運作, 還有他跟SynchronizationContext 之間的關係, 我們可以從 Dispatcher 的 constructor  知道 Dispatcher  本身建立了一個 MessageOnlyHwndWrapper 物件, 而該 Class 繼承至 HwndWrapper, HwndWrapper 的 constructor 會負責使用 Win32 API 來建立一個看不見的 Window 並且註冊自有的 WndProc , 而 Dispatcher 會利用 hook 的方式來被 message pump 所推動. 






[SecurityCritical, SecurityTreatAsSafe]
private Dispatcher()
{
    this._instanceLock = new object();
    this._timers = new List();
    this._queue = new PriorityQueue();
    _tlsDispatcher = this;
    this._dispatcherThread = Thread.CurrentThread;
    lock (_globalLock)
    {
        _dispatchers.Add(new WeakReference(this));
    }
    this._unhandledExceptionEventArgs = 
          new DispatcherUnhandledExceptionEventArgs(this);
    this._exceptionFilterEventArgs = 
          new DispatcherUnhandledExceptionFilterEventArgs(this);
    MessageOnlyHwndWrapper wrapper = new MessageOnlyHwndWrapper();
    this._window = new SecurityCriticalData(wrapper);
    this._hook = new HwndWrapperHook(this.WndProcHook);
    this._window.Value.AddHook(this._hook);
}

所以, 我們知道 Dispatcher 所依附的 thread 是需要 message pump 的, 也就是該 thread 事先必須調用過 Dispatcher.Run 而 Run 回去調用 Dispatcher.PushFrameImpl 來轉動 message pump, 最簡單的例子 Application.Run 內部就是間接調用 Dispatcher.PushFrameImpl 來實作 message pump 給 UI 使用. 而執行 Dispatcher.PushFrameImpl 的過程會去 overwrite 該 thread 的 SynchronizationContext 將原本的 context 換成 DispatcherSynchronizationContext.

private void PushFrameImpl(DispatcherFrame frame)
{
    SynchronizationContext syncContext = null;
    SynchronizationContext context2 = null;
    MSG msg = new MSG();
    this._frameDepth++;
    try
    {
        syncContext = SynchronizationContext.Current;
        context2 = new DispatcherSynchronizationContext(this);
        SynchronizationContext.SetSynchronizationContext(context2);
        try
        {
            while (frame.Continue)
            {
                if (!this.GetMessage(ref msg, IntPtr.Zero, 0, 0))
                {
                    break;
                }
                this.TranslateAndDispatchMessage(ref msg);
            }
            if ((this._frameDepth == 1) && this._hasShutdownStarted)
            {
                this.ShutdownImpl();
            }
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(syncContext);
        }
    }
    finally
    {
        this._frameDepth--;
        if (this._frameDepth == 0)
        {
            this._exitAllFrames = false;
        }
    }
}
如果在一個沒有 message pump 的 thread 中取得 Dispatcher 會怎樣呢? Dispatcher.CurrentDispatcher 會因為找不到對應的 dspatcher 而 new 一個新的出來, 當然, 對這個 new 出來的 dspatcher 使用 DispatcherPriority.Send priority 來調用 Dispatcher.Invoke 是會有用的, 因為 Dispatcher.InvokeImpl 會直接去執行 invoke 指定的 delegate, 特別有趣的是, 執行過程中Dispatcher.InvokeImpl 會去維護 SynchronizationContext, 所以他會去設定當下的 context 並且在執行完後再設定回來. 而非同步的 case 中 DispatcherOperation.InvokeImpl 也會去維護 context. 




[SecurityTreatAsSafe, FriendAccessAllowed, SecurityCritical]
internal object InvokeImpl(DispatcherPriority priority, TimeSpan timeout, 
  Delegate method, object args, bool isSingleParameter)
{
    ValidatePriority(priority, "priority");
    if (priority == DispatcherPriority.Inactive)
    {
        throw new ArgumentException(SR.Get("InvalidPriority"), "priority");
    }
    if (method == null)
    {
        throw new ArgumentNullException("method");
    }
    if ((priority == DispatcherPriority.Send) && this.CheckAccess())
    {
        SynchronizationContext current = SynchronizationContext.Current;
        try
        {
            SynchronizationContext.SetSynchronizationContext(
                 new DispatcherSynchronizationContext(this));
            return this.WrappedInvoke(method, args, isSingleParameter);
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(current);
        }
    }
    DispatcherOperation operation = this.BeginInvokeImpl(priority, 
             method, args, isSingleParameter);
    if (operation != null)
    {
        operation.Wait(timeout);
        if (operation.Status == DispatcherOperationStatus.Completed)
        {
            return operation.Result;
        }
        if (operation.Status == DispatcherOperationStatus.Aborted)
        {
            return null;
        }
        operation.Abort();
    }
    return null;
}

但其他的 priority 就沒機會執行了, 因為其它的 priority 會使用 Dispatcher.BeginInvokeImpl 來達到非同步的執行, 而骨子裡還是使用 PostMessage 這個 Win32 API 去產生 message 給 message queue, 但是這個 queue 將不會有人去調用 GetMessage 所以所有工作都不會被直行.