wpf dispacher 实现原理分析

一,作用

提供线程队列服务,通过windows message 这个基本原理逻辑代码。

二,代码

异步调用

dispath 代码 :https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs,9e84b372d672e449,references

dispacher 把我们行为封装成为DispatcherOperation,然后投递队列,同时投递事件给绑定的线程对应的 message only 窗口,窗口收到消息就运行我的行为。

写c++开发时候,为了拥有这样功能,我们都要自己写窗口,封装异步行为,到了WPF 这个已经是自带功能。

private void InvokeAsyncImpl(DispatcherOperation operation, CancellationToken cancellationToken)
        {
            DispatcherHooks hooks = null;
            bool succeeded = false;
 
            // Could be a non-dispatcher thread, lock to read
            lock(_instanceLock)
            {
                if (!cancellationToken.IsCancellationRequested &&
                    !_hasShutdownFinished &&
                    !Environment.HasShutdownStarted)
                {
                    // Add the operation to the work queue
                    //当前行为添加到队列里面去
                    operation._item = _queue.Enqueue(operation.Priority, operation);
 
                    // Make sure we will wake up to process this operation.
                    //投递消息给创建window,这样子处理消息
                    succeeded = RequestProcessing();
 
                    if (succeeded)
                    {
                        // Grab the hooks to use inside the lock; but we will
                        // call them below, outside of the lock.
                        hooks = _hooks;
                    }
                    else
                    {
                        // Dequeue the item since we failed to request
                        // processing for it.  Note we will mark it aborted
                        // below.
                        _queue.RemoveItem(operation._item);
                    }
                }
            }
 
            if (succeeded == true)
            {
                // We have enqueued the operation.  Register a callback
                // with the cancellation token to abort the operation
                // when cancellation is requested.
                if(cancellationToken.CanBeCanceled)
                {
                    CancellationTokenRegistration cancellationRegistration = cancellationToken.Register(s => ((DispatcherOperation)s).Abort(), operation);
 
                    // Revoke the cancellation when the operation is done.
                    operation.Aborted += (s,e) => cancellationRegistration.Dispose();
                    operation.Completed += (s,e) => cancellationRegistration.Dispose();
                }
 
                if(hooks != null)
                {
                    hooks.RaiseOperationPosted(this, operation);
                }
 
                if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordDispatcher | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info))
                {
                    EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientUIContextPost, EventTrace.Keyword.KeywordDispatcher | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, operation.Priority, operation.Name, operation.Id);
                }
            }
            else
            {
                // We failed to enqueue the operation, and the caller that
                // created the operation does not expose it before we return,
                // so it is safe to modify the operation outside of the lock.
                // Just mark the operation as aborted, which we can safely
                // return to the user.
                operation._status = DispatcherOperationStatus.Aborted;
                operation._taskSource.SetCanceled();
            }
        }

创建dispacher

  1. 添加全局list dispacher【全局管理】
  2. 创建MessageOnlyHwndWrappe 窗口,这个是核心的功能。
  3. 添加 窗口消息处理函数 WndProcHook
     [SecurityCritical, SecurityTreatAsSafe]
        private Dispatcher()
        {
            _queue = new PriorityQueue<DispatcherOperation>();
 
            _tlsDispatcher = this; // use TLS for ownership only
            _dispatcherThread = Thread.CurrentThread;
 
            // Add ourselves to the map of dispatchers to threads.
            lock(_globalLock)
            {
                //全局list 增加 当前的list
                _dispatchers.Add(new WeakReference(this));
            }
 
            _unhandledExceptionEventArgs = new DispatcherUnhandledExceptionEventArgs(this);
            _exceptionFilterEventArgs = new DispatcherUnhandledExceptionFilterEventArgs(this);
 
            _defaultDispatcherSynchronizationContext = new DispatcherSynchronizationContext(this);
 
            // Create the message-only window we use to receive messages
            // that tell us to process the queue.
            MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();
            _window = new SecurityCriticalData<MessageOnlyHwndWrapper>( window );
 
            _hook = new HwndWrapperHook(WndProcHook);
            _window.Value.AddHook(_hook);
 
            // DDVSO:447590
            // Verify that the accessibility switches are set prior to any major UI code running.
            AccessibilitySwitches.VerifySwitches(this);
        }

WndProcHook

处理窗口消息,用来处理 我们投递过来的异步操作

       private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            WindowMessage message = (WindowMessage)msg;
            if(_disableProcessingCount > 0)
            {
                throw new InvalidOperationException(SR.Get(SRID.DispatcherProcessingDisabledButStillPumping));
            }
 
            if(message == WindowMessage.WM_DESTROY)
            {
                if(!_hasShutdownStarted && !_hasShutdownFinished) // Dispatcher thread - no lock needed for read
                {
                    // Aack!  We are being torn down rudely!  Try to
                    // shut the dispatcher down as nicely as we can.
                    ShutdownImpl();
                }
            }
            else if(message == _msgProcessQueue)
            {
                //如果消息,异步投递消息
                //这个就是我们用dispacher beginInvoke的
                // 内部调用 RequestProcessing(); 投递回调消息过来
                ProcessQueue();
            }
            else if(message == WindowMessage.WM_TIMER && (int) wParam == TIMERID_BACKGROUND)
            {
                // This timer is just used to process background operations.
                // Stop the timer so that it doesn't fire again.
                SafeNativeMethods.KillTimer(new HandleRef(this, hwnd), TIMERID_BACKGROUND);
 
                ProcessQueue();
            }
            else if(message == WindowMessage.WM_TIMER && (int) wParam == TIMERID_TIMERS)
            {
                // We want 1-shot only timers.  So stop the timer
                // that just fired.
                KillWin32Timer();
 
                PromoteTimers(Environment.TickCount);
            }
 
            // We are about to return to the OS.  If there is nothing left
            // to do in the queue, then we will effectively go to sleep.
            // This is the condition that means Idle.
            DispatcherHooks hooks = null;
            bool idle = false;
 
            lock(_instanceLock)
            {
                idle = (_postedProcessingType < PROCESS_BACKGROUND);
                if (idle)
                {
                    hooks = _hooks;
                }
            }
 
            if (idle)
            {
                if(hooks != null)
                {
                    hooks.RaiseDispatcherInactive(this);
                }
 
                ComponentDispatcher.RaiseIdle();
            }
 
            return IntPtr.Zero ;
        }

获取当前dispatcher

  1. 通过FromThread 函数获取 当前线程的dispacher
  2. 如果当前线程不存在dispacher,那么直接创建。
       public static Dispatcher CurrentDispatcher
        {
            get
            {
                // Find the dispatcher for this thread.
                Dispatcher currentDispatcher = FromThread(Thread.CurrentThread);;
 
                // Auto-create the dispatcher if there is no dispatcher for
                // this thread (if we are allowed to).
                if(currentDispatcher == null)
                {
                    currentDispatcher = new Dispatcher();
                }
 
                return currentDispatcher;
            }
        }

我们可以new Thread ,然后执行获当前dispacher。

消息循环

        //<SecurityNote>
        // Critical - as this calls critical methods (GetMessage, TranslateMessage, DispatchMessage).
        // TreatAsSafe - as the critical method is not leaked out, and not controlled by external inputs.
        //</SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe ]
        private void PushFrameImpl(DispatcherFrame frame)
        {
            SynchronizationContext oldSyncContext = null;
            SynchronizationContext newSyncContext = null;
            MSG msg = new MSG();
 
            _frameDepth++;
            try
            {
                // Change the CLR SynchronizationContext to be compatable with our Dispatcher.
                oldSyncContext = SynchronizationContext.Current;
                newSyncContext = new DispatcherSynchronizationContext(this);
                SynchronizationContext.SetSynchronizationContext(newSyncContext);
 
                try
                {
                    while(frame.Continue)
                    {
                        if (!GetMessage(ref msg, IntPtr.Zero, 0, 0))
                            break;
 
                        TranslateAndDispatchMessage(ref msg);
                    }
 
                    // If this was the last frame to exit after a quit, we
                    // can now dispose the dispatcher.
                    if(_frameDepth == 1)
                    {
                        if(_hasShutdownStarted)
                        {
                            ShutdownImpl();
                        }
                    }
                }
                finally
                {
                    // Restore the old SynchronizationContext.
                    SynchronizationContext.SetSynchronizationContext(oldSyncContext);
                }
            }
            finally
            {
                _frameDepth--;
                if(_frameDepth == 0)
                {
                    // We have exited all frames.
                    _exitAllFrames = false;
                }
            }
        }

如果普通的windwos 窗口程序,一般只有一个消息循环,消息循环每个线程必须要有一个。如果你创建windows不在UI线程,那么必须自己循环,不然程序跑不起来。

Appliaction 继承了 DispatcherObject,那么默认创建一个dispacker的对象。

      /// <summary>
        ///     Instantiate this object associated with the current Dispatcher.
        /// </summary>
        protected DispatcherObject()
        {
            _dispatcher = Dispatcher.CurrentDispatcher;
        }

可以退出,如果wpf起码有2个窗口,dispacher创建窗口。

wpf 程序会调用dispacher Run

我通过自己写c++ 工具搜索消息窗口(消息窗口不能被枚举函数枚举,所以你用spy++是无法查找到这个窗口,必须通过下面FindWindowEx函数才可以)

void find_hwnd_message(HWND hw){
	HWND child_hwnd = FindWindowEx(HWND_MESSAGE, hw, NULL, NULL);
	if(child_hwnd == NULL){
		return;
	}

	wchar_t class_name[256] = {0};
	GetClassName(hw, class_name, 255);
	wprintf_s(L"class_name: %s\n", class_name );
	find_hwnd_message(child_hwnd);
}

然后再main函数调用 find_hwnd_message (null); 这样子可以查询所有的消息窗口,你也可以填写class name 和 windows name 做过滤。

三、总结

application 启动时候就默认创建 dispacher,通过Dispacher 获取当前的,当前不存在就默认创建逻辑,然后启动主窗口,然后调用当前dispacher中Run ,用来消息循环,从而整个window 应用程序核心逻辑走完了。dispacher会绑定线程,会创建一个消息窗口。通过原理,我们可以推出来,dispacher是不能在很短时间做大量异步行为(如果消息还消耗时间的话),因为dispacher 消息处理不过来,阻塞界面。

四、资料

微软消息窗口文档介绍