老周以其一贯的风格,今天将为大家讲解一些WPF中的Dispatcher相关知识。虽然话题有些深奥,但老周会力求简单易懂。
现在先来个小活,相信老周N年前写的一篇水文中提到过。在构建应用程序时,主线程通常是UI线程。我们在主窗口放一个居中对齐的按钮,处理按钮的单击事件。在一个线程上创建可视化资源,要求是STA模式。也就是说,UI对象只能在创建它的线程上直接访问,否则需要进行封送。Dispatcher类负责在线程上创建消息循环,使窗口能够响应各种事件。DispatcherFrame类表示一个消息循环,有一个属性叫Continue,表示这个“帧”是否持续。
Dispatcher类的PushFrame方法用于启动消息循环。一个帧表示一层消息循环,PushFrame方法实际上是调用了PushFrameImpl方法。Continue属性决定是否继续循环。正确的做法是定义一个窗口集合管理类,当打开的窗口数量为0时,只调用一次ExitAllFrames方法即可。另外,Dispatcher类没有公开的构造函数,可以通过Dispatcher.CurrentDispatcher属性、Dispatcher.FromThread()方法或者已创建WPF对象的Dispatcher属性获取与当前线程关联的Dispatcher实例。
另一个重要对象是DispatcherOperation及其队列。Dispatcher类使用RegisterWindowMessage函数向系统注册了一个自定义消息,用来处理队列中的DispatcherOperation对象。DispatcherOperation对象封装了传给Dispatcher对象的委托引用,并添加到队列中。ProcessQueue方法由自定义消息触发,并从队列中取出一个DispatcherOperation对象来运行。DispatcherOperation即使没有键盘、鼠标等动作也可以触发,因为队列运转用的是定时器。
处理一些耗时操作时,可以通过多线程来避免UI线程卡死。耗时操作的过程可以拆分出多个小段,每段时间很短。在每小段代码执行前或执行后让消息循环动一下,可以避免UI线程卡死。根据前面的分析,要让消息循环转动,就要向调度代码插入一帧,同时也要用Invoke等方法插入一个委托。官方给的DoEvents例子其实就是这个原理。
今天就水到这里了。老周要准备去参加项目上的码农朋友的聚会,大家一起吃大锅饭,场面可能比较热闹。