几个月之前因为项目需要,需要实现一个类似于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); } }