接上文: Windows 消息循环(1) - 概览
win32/MFC/WinForm/WPF 都依靠消息循环驱动,让程序跑起来。
本文介绍 WPF 中是如何使用消息循环来驱动程序的。
4 消息循环在 WPF 中的应用
4.1 引入
只听说过 Dispatcher ,哪里来的消息循环?
先瞧一眼 WPF 启动运行堆栈:
可以发现 PushFrameImpl
这个方法。
去看其源码,就发现了熟悉的消息循环 :
可以理解为:Dispatcher 对消息循环的操作进行了“封装” 。
那,Dispatcher 是谁?
4.2 Dispatchcer
Provides services for managing the queue of work items for a thread.
提供用于管理线程工作项队列的服务。
大部分WPF对象,都是 DispatcherObject。这意味着,可以在 DispatcherObject 中(如 Window 中),
使用 this.Dispatchcer 获取到 Dispatchcer 。
一般我们会通过三种方式获取 Dispatchcer :
// App.Current.Dispatcher;(Application.Current.Dispatcher)
var dispatcher1 = App.Current.Dispatcher;
// CurrentDispatcher;
var dispatcher2 = System.Windows.Threading.Dispatcher.CurrentDispatcher;
// System.Windows.Threading.DispatcherObject.Dispatcher;
var dispatcher3 = this.Dispatcher;
可分为两类:
当前线程的 Dispatcher:
System.Windows.Threading.Dispatcher.CurrentDispatcher;创建对应对象的 Dispatcher:
App.Current.Dispatcher;
DispatcherObject.Dispatcher;
可参见:
Why not Dispather.CurrentDispatcher - haungtengxiao
Dispatcher 和线程是什么关系?
- Dispatcher 属于线程(与线程一一对应)。
- WPF的对象在获取this.Dispatcher属性时,不同对象取的都是同一个Dispatcher实例。(因为都是同一个UI线程创建的。)
- 在默认的 WPF UI线程中: App.Current.Dispatcher = DispatcherObject.Dispatcher
所有的线程(UI线程,普通线程)都有 Dispatcher 吗?
是的。
在所有线程中,调用 System.Windows.Threading.Dispatcher.CurrentDispatcher
都会得到一个属于这个线程的 Dispatcher 对象。(不用的时候不会创建)
所以:如果你想在一个后台线程中,使用 Dispatcher.CurrentDispatcher.Invoke
将操作封送到 UI 线程,是做不到的。因为这时候获取到的 Dispatcher 不是UI线程的 Dispatcher, 而是当前线程自己的 Dispatcher。
4.3 Dispatcher 如何实现跨线程的调用。
最常使用 Dispatcher 的创建就是,在后台线程更新 UI ,那 Dispatcher 是如何做到的呢。
当你调用
Application.Current.Dispatcher.Invoke(() =>
{
SendMessageBtn.Content = "更新按钮";
});
时,Dispatcher 究竟做了什么,把操作转移到 UI 线程上去了。
关于 Invoke,InvokeSync,BeginInvoke 的区别,参见:
深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分) - walterlv
将调用的Delegate和优先级包装成一个DispatcherOperation放入Dispatcher维护的优先级队列当中,这个Queue是按DispatcherPriority排序的,总是高优先级的DispatcherOperation先被处理。
往当前线程的消息队列当中Post一个名为MsgProcessQueue的Message。(这个消息是WPF自己定义的。)这个消息被Post到消息队列之前,还要设置MSG.Handle,这个Handle就是Window 1#的Handle。指定Handle是为了在消息循环Dispatch消息的时候,指定哪个窗口的 WndProc 处理这个消息。
消息循环读取消息。
系统根据获取消息的Handle,发现跟Window1#的Handle相同,那么这个消息派发到Window1#的窗口过程,让其处理。
在窗口过程中,优先级队列当中取一个DispatcherOperation。
执行DispatcherOperation.Invoke方法,Invoke方法的核心就是调用DispatcherOperation构造时传入的Delegate,也就是Dispatcher.BeginInvoke传入的Delegate。最终这个Foo()方法就被执行了。
4.4 回顾
- WPF 底层仍然靠信息循环来驱动。
- Dispatcher 使用消息循环来实现跨进程的委托调用。
- Dispatcher 属于线程,需要理解当前拿到的 Dispatcher 到底是哪个 Dispatcher 。
参考资料:
SendMessage、PostMessage原理-大白菜-51CTO博客
WPF的消息机制(一)- 让应用程序动起来 - 葡萄城技术团队 - 博客园
WPF的消息机制(二)- WPF内部的5个窗口之隐藏消息窗口 - 葡萄城技术团队 - 博客园
WPF的消息机制(三)- WPF内部的5个窗口之处理激活和关闭的消息窗口以及系统资源通知窗口 - 葡萄城技术团队 - 博客园
Why not Dispather.CurrentDispatcher - haungtengxiao