WPF Dispatcher 一次小重構

     幾個月以前由於項目須要,須要實現一個相似於WPF Dispatcher類的類,來實現一些線程的調度。以前由於一直作Asp.Net,根本沒有鑽到這個層次去,作的過程當中,諸多不順,重構了四五次,終於實現,知足項目要求。 Dispatcher的源碼對我來講,看的確實很累,各類累關聯,不知所云。當時僅有的周永恆的博客看了六七遍也只是知道了大概的輪廓。今天我這裏講的,就是按照當時的項目需求的方向所理解和收穫的一些知識,分享出來。太全面的東西,我講出來只怕誤人子弟了,你們仍是去參照老周的博客吧。O(∩_∩)O~html

1、Dispatcher幾個重要的方法。 windows

      Dispatcher譯名是調度者,他的工做就是不斷的從線程的消息隊列中取出一個消息,經過TranslateAndDispatchMessage派發出去,WndProcHook來處理捕獲到的消息。app

      1.Run()dom

      Run()是dispatcher的一個靜態方法,這個方法在WPF應用程序運行起來以前,已經被調用了,能夠經過調用堆棧來看到。ide

       

      而Run()調用的是一個所謂的消息泵,---》PushFrame(new DispatcherFrame());---》PushFrameImpl(frame); 函數

      它的源碼以下,關鍵的就是拿個while循環,經過GetMessage來不斷的獲取消息,而後派發出去。學習

 [SecurityCritical, SecurityTreatAsSafe ] 
        private void PushFrameImpl(DispatcherFrame frame)
        { 
            MSG msg = new MSG();
                //.............................
                    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 
             //................. 略了一些代碼
        }

      而這裏的GetMessage和TranslateAndDispactchMessage 本質上都是調用的Win32的API函數。和GetMessage對應的還有個PeekMessage,前者一直等待有消息到才返回,後者獲取一次就返回。上文的意思就是趕上錯誤或者窗口退出消息就中斷。在WPF中還有一個隱藏的窗口,是窗口類,但沒有窗體,專門來捕獲派發過來的消息(工做線程中委Invoke或者BeginInvoke的方法也是包裝成了消息)。在Dispatcher的構造函數中:ui

           MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper(); 
            _window = new SecurityCriticalData<MessageOnlyHwndWrapper>( window ); 
            _hook = new HwndWrapperHook(WndProcHook); 
            _window.Value.AddHook(_hook);

 而在WndProcHook 方法中,主要的就是處理消息。而其中又調用了ProcessQueue() 這個方法。this

  2. ProcessQueue(); spa

 在ProcessQueue中,主要是處理BeginInvoke和Invoke 進入隊列的任務(或者說操做)。關鍵代碼以下,從隊列中 dequeue()出來後,invoke執行了。而這裏的DispatcherOperation就包含了咱們以前Invoke或者BeginInvoke進來的操做,到這裏就是正真被執行了。各個版本的源碼會有些出入,下載安裝的.net4.0源碼和反編譯出來的源碼是有些出入的。

  private void ProcessQueue() 
        { 
 DispatcherPriority maxPriority = DispatcherPriority.Invalid; // NOTE: should be Priority.Invalid
            DispatcherOperation op = null; 
            DispatcherHooks hooks = null;
   //..............
                    {
                         op = _queue.Dequeue();
                         hooks = _hooks;
                    } 
// .........
                   op.Invoke(); 

  //..........
      }

3.BeginInvokeImpl()

 上面好像有點倒敘,講了出隊列,沒有說入隊列。 咱們在工做線程調用的方法,以下

   private void ButtonOnclick(object sender, EventArgs e)
        {
            Button.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                {
                    Button.Content = _random.NextDouble();//顯示一個隨機值。
                }));
        }

BeginInvoke和Invoke,Dispatcher都重載了多個版本,Invoke的最終調用的是InvokeImpl,BeginInvoke最終調用的是BeginInvokeImpl,而InvokeImpl內部仍是調用的BeginInvokeImpl。 在這個方法中關鍵代碼以下:

  [FriendAccessAllowed] //used by DispatcherExtensionMethods in System.Windows.Presentation.dll 
        internal DispatcherOperation BeginInvokeImpl(DispatcherPriority priority, Delegate method, object args, int numArgs)
        {
            ValidatePriority(priority, "priority");
            if(method == null) 
            {
                throw new ArgumentNullException("method"); 
            } 

            DispatcherOperation operation = null; DispatcherHooks hooks = null;
            bool succeeded = false;

            // Could be a non-dispatcher thread, lock to read 
            lock(_instanceLock)
            { 
                if (!_hasShutdownFinished && !Environment.HasShutdownStarted) 
                {
                    operation = new DispatcherOperation(this, method, priority, args, numArgs); 

                    // Add the operation to the work queue
                    operation._item = _queue.Enqueue(priority, operation);
 
                    // Make sure we will wake up to process this operation.
                    succeeded = RequestProcessing(); 
 
                    if (succeeded)
                    { 
                        hooks = _hooks;
                    }
                    else
                    { 
                        // Dequeue and abort the item.  We can safely return this to the user.
                        _queue.RemoveItem(operation._item); 
                        operation._status = DispatcherOperationStatus.Aborted; 
                    }
                } 
                else
                {
                    // Rather than returning null we'll create an aborted operation and return it to the user
                    operation = new DispatcherOperation(this, method, priority); 
                }
            } 
 
        // .................return operation;
        } 

經過Enqueue 進入了消息隊列。而這裏的_queue的定義是 :

  private PriorityQueue<DispatcherOperation> _queue;

就是一個帶有優先級的操做隊列。

2、實現一個小的Dispatcher

   找到了上面幾個重要點,就能夠構建本身的調度者了。(不得不說一下,剛打死了一直小老鼠。這麼惡劣的公司宿舍,點燃一支菸,壓壓驚,繼續寫代碼,不容易啊)

   1.本身的RUN()

  [SecurityCritical, UIPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public void Run()
        {
            var waitHandles = new WaitHandle[2];// 這裏是項目須要的兩個handle,  _stopEvent = new AutoResetEvent(false); 專門處理程序窗口中止。
            waitHandles[0] = _stopEvent;
            waitHandles[1] = _triggerEvent;

            var msg = new MSG();
            while (!_isShutdown)
            {
                var index = MsgWaitForMultipleObjects(waitHandles, false, Infinite, 0x04BF);//等待事件函數,他能等待到是否觸發了waitHandles中的handle 其餘的消息交給peekmessage處理
                switch (index)
                {
                    case 0:
                        _isShutdown = true;
                        _stopEvent.Reset();
                        break;
                    case 1:
                        _triggerEvent.Reset();
                        while (TaskQueueCount > 0)
                        {
                            ProcessQueue();//處理invoke和beginInvoke。
                        }
                        break;
                    default:
                        while (PeekMessage(ref msg))
                        {
                            TranslateAndDispatchMessage(ref msg);
                        }
                        break;
                }
            }
        }
MsgWaitForMultipleObjects:
  private int MsgWaitForMultipleObjects(WaitHandle[] wHandles, bool fWaitAll, int dwMilliseconds, int dwWakeMask)
        {
            var i = wHandles.Length;
            var intPtrs = new IntPtr[i];
            for (int j = 0; j < i; j++)
            {
                intPtrs[j] = wHandles[j].SafeWaitHandle.DangerousGetHandle();//這個轉換的方法,找了兩天。不容易啊。handle轉化爲inPtrs
            }
            return UnsafeNativeMethods.MsgWaitForMultipleObjects(i, intPtrs, fWaitAll, dwMilliseconds, dwWakeMask);//這個方法能夠去查MSDN,也是個WIN32函數,下面的UnsafeNativeMethods中有寫。
        }

 ProcessQueue

 public void ProcessQueue()
        {
            DispatcherOperation operation = null;
            var invalid = _queue.MaxPriority;
            if (((invalid != DispatcherPriority.Invalid) && (invalid != DispatcherPriority.Inactive)) || _queue.Count == 0)
            {
                operation = _queue.Dequeue();
            }
            if (operation != null)
            {
                operation.Invoke();
                operation.InvokeCompletions();
            }
        }

TranslateAndDispatchMessage 和 PeekMessage 

[SecurityCritical]
        private bool PeekMessage(ref MSG msg)
        {
            var nullHandleRef = new HandleRef(null, IntPtr.Zero);
            return UnsafeNativeMethods.PeekMessage(ref msg, nullHandleRef, 0, 0, 1);
        }
  [SecurityCritical]
        private void TranslateAndDispatchMessage(ref MSG msg)
        {
            bool handled = ComponentDispatcher.RaiseThreadMessage(ref msg);

            if (!handled)
            {
                UnsafeNativeMethods.TranslateMessage(ref msg);
                UnsafeNativeMethods.DispatchMessage(ref msg);
            }
        }
View Code

UnsafeNativeMethods精簡了不少。

 public class UnsafeNativeMethods
    {
        [SuppressUnmanagedCodeSecurity, SecurityCritical, DllImport("user32.dll", EntryPoint = "GetMessageW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
        private static extern int IntGetMessageW([In, Out] ref MSG msg, HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax);
       
        /// <summary>
        /// TranslateMessage
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        [SecurityCritical, SuppressUnmanagedCodeSecurity, DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        internal static extern bool TranslateMessage([In, Out] ref MSG msg);
       
        /// <summary>
        /// DispatchMessage
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        [SecurityCritical, SuppressUnmanagedCodeSecurity, DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern IntPtr DispatchMessage([In] ref MSG msg);
      
        [SuppressUnmanagedCodeSecurity, SecurityCritical, DllImport("user32.dll", EntryPoint = "MsgWaitForMultipleObjectsEx", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        private static extern int IntMsgWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, int dwMilliseconds, int dwWakeMask, int dwFlags);

        /// <summary>
        /// 等待消息和事件
        /// </summary>
        /// <param name="nCount"></param>
        /// <param name="pHandles"></param>
        /// <param name="fWaitAll"></param>
        /// <param name="dwMilliseconds"></param>
        /// <param name="dwWakeMask"></param>
        /// <returns></returns>
        [SuppressUnmanagedCodeSecurity, SecurityCritical, DllImport("user32.dll", EntryPoint = "MsgWaitForMultipleObjects", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        internal static extern int MsgWaitForMultipleObjects(int nCount, IntPtr[] pHandles, bool fWaitAll, int dwMilliseconds, int dwWakeMask);

        /// <summary>
        /// PeekMessage
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="hwnd"></param>
        /// <param name="msgMin"></param>
        /// <param name="msgMax"></param>
        /// <param name="remove"></param>
        /// <returns></returns>
        [SecurityCritical, SuppressUnmanagedCodeSecurity, DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern bool PeekMessage([In, Out] ref MSG msg, HandleRef hwnd, int msgMin, int msgMax, int remove);
      
        [ComImport, SecurityCritical, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("8f1b8ad8-0b6b-4874-90c5-bd76011e8f7c"), SuppressUnmanagedCodeSecurity]
        internal interface ITfMessagePump
        {
            [SecurityCritical]
            void PeekMessageA(ref MSG msg, IntPtr hwnd, int msgFilterMin, int msgFilterMax, int removeMsg, out int result);
            [SecurityCritical]
            void GetMessageA(ref MSG msg, IntPtr hwnd, int msgFilterMin, int msgFilterMax, out int result);
            [SecurityCritical]
            void PeekMessageW(ref MSG msg, IntPtr hwnd, int msgFilterMin, int msgFilterMax, int removeMsg, out int result);
            [SecurityCritical]
            void GetMessageW(ref MSG msg, IntPtr hwnd, int msgFilterMin, int msgFilterMax, out int result);
        }

        /// <summary>
        /// 獲取消息
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="hWnd"></param>
        /// <param name="uMsgFilterMin"></param>
        /// <param name="uMsgFilterMax"></param>
        /// <returns></returns>
        /// <exception cref="Win32Exception"></exception>
        [SecurityCritical]
        public static bool GetMessageW([In, Out] ref MSG msg, HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax)
        {
            var index = IntGetMessageW(ref msg, hWnd, uMsgFilterMin, uMsgFilterMax);
            switch (index)
            {
                case -1:
                    throw new Win32Exception();

                case 0:
                    return false;
            }
            return true;
        }

        [SecurityCritical]
        internal static int MsgWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, int dwMilliseconds, int dwWakeMask, int dwFlags)
        {
            int num = IntMsgWaitForMultipleObjectsEx(nCount, pHandles, dwMilliseconds, dwWakeMask, dwFlags);
            if (num == -1)
            {
                throw new Win32Exception();
            }
            return num;
        }
    }
View Code

其餘的類,差很少都是拿來主義。還有一些設計公司內部的東西,不便貼出來,但核心的東西都說了。

3、調用

 1.咱們能夠把操做壓入本身的dispatcher中。

  Dispather.BeginInvoke(new Action<object>(TriggerAction), DispatcherPriority.Normal, 1);

2.讓timer跑起來

      public MainWindow()
        {
            InitializeComponent();
            _thread = new Thread(Run);
            _thread.Start();
            var id = Thread.CurrentThread.ManagedThreadId;
        }
     
        [SecurityCritical, UIPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        private void Run()
        {
            var mytimer = new System.Windows.Forms.Timer();//windows form中的timer 必須依賴一個窗體線程,若是沒有這個下面的_dispatcher.Run() tick中的方法          是不會有效果的。
            mytimer.Tick += ButtonOnclick;
mytimer.Enabled
= true;
mytimer.Interval
= 300;
//var id = Thread.CurrentThread.ManagedThreadId;
_dispatcher.Run();//這是必須的 這裏純粹只是一個例子 在這裏沒有使用意義。
}

  結語:學習源碼確實有不少收穫,可是確實很累,要是沒有別人指點,我幾乎是不可能作出來的,特別是對核心代碼的提煉,很難分清,重要的一點就是要明白需求。以前也沒有接觸過底層的函數,可能作C++的人接觸的比較多,像windows的消息機制,這裏理解起來就比我要快的多了。在這裏分享出來,與君共勉!若是對你有幫助,就支持一個吧。

寫完11點多了,洗洗睡了。園友們晚安!

相關文章
相關標籤/搜索