• WPF Dispatcher 一次小重构


         几个月之前因为项目需要,需要实现一个类似于WPF Dispatcher类的类,来实现一些线程的调度。之前因为一直做Asp.Net,根本没有钻到这个层次去,做的过程中,诸多不顺,重构了四五次,终于实现,满足项目要求。 Dispatcher的源码对我来说,看的确实很累,各种累关联,不知所云。当时仅有的周永恒的博客看了六七遍也只是知道了大概的轮廓。今天我这里讲的,就是按照当时的项目需求的方向所理解和收获的一些知识,分享出来。太全面的东西,我讲出来只怕误人子弟了,大家还是去参照老周的博客吧。O(∩_∩)O~

    一、Dispatcher几个重要的方法。 

          Dispatcher译名是调度者,他的工作就是不断的从线程的消息队列中取出一个消息,通过TranslateAndDispatchMessage派发出去,WndProcHook来处理捕获到的消息。

          1.Run()

          Run()是dispatcher的一个静态方法,这个方法在WPF应用程序运行起来之前,已经被调用了,可以通过调用堆栈来看到。

           

          而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的构造函数中:

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

     而在WndProcHook 方法中,主要的就是处理消息。而其中又调用了ProcessQueue() 这个方法。

      2. ProcessQueue(); 

     在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;

    就是一个带有优先级的操作队列。

    二、实现一个小的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

    其他的类,差不多都是拿来主义。还有一些设计公司内部的东西,不便贴出来,但核心的东西都说了。

    三、调用

     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点多了,洗洗睡了。园友们晚安!

  • 相关阅读:
    视图创建
    根据表格作业题
    表格 作业题练习
    创建表格 练习题
    聚合函数、数学函数、日期时间函数
    接口自动化框架
    python+request+Excel 几十份接口测试用例一起自动化测试的接口测试框架
    python3 函数
    pip源头
    爬虫
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/3383355.html
Copyright © 2020-2023  润新知