在上一篇中我们分析了Win32和WinForm编写GUI应用程序会面对的主要问题。总结下来最重要的就是:如何高效的从Worker线程中更新界面。所以首先让我们看看WPF中是如何达到这个目的的。 DispatcherObject的使用DispatcherObject类有两个成员方法,CheckAccess和VerifyAccess。CheckAccess功能和WinForm中Control.InvokeRequired属性相同,当调用线程与对象的创建线程不是同一个实例时,返回False。VerifyAccess则是抛出异常。 DispatcherObject还有一个Dispatcher的属性,返回控件的创建Dispatcher。通过其Invoke方法,我们可以将界面更新的操作Marshal到控件的创建线程。如下例:
1
TextBox textbox
=
new
TextBox();
2 if ( ! textbox.CheckAccess) 3 { 4 textbox.Dispatcher.Invoke(DispatcherPriority.Send, delegate { }); 5 } CheckAccess()的实现方法类似于上篇的最后一个例子,通过比较两个线程实例,因而速度很快,只需要几个IL指令。继承DispatcherObject的类,在内部都调用VerifyAccess来做判断,因而当非创建线程调用时,就会抛出异常。这样的好处是我们可以较早的发现代码的问题,不像WinForm那样不确定的发生。 好了,下面我们来看看真正做事情的Dispatcher类,在这之前让我们再回顾一个Win32的消息循环。 Win32 消息循环在Win32中,消息Pump的建立是通过循环的调用GetMessage,当收到WM_Quit类型的消息是,退出。而我们对窗口或控件属性的修改,如颜色,位置都实际都是调用PostMessage或SendMessage。当调用线程与窗口的Owner线程是同一个,则SendMessage直接调用窗口函数;如不同则将消息放置在窗口对应的消息队列中,由GetMessage取出并进行处理。WinForm实际是建立在Win32的基础上,所以当我们写lable.Text = "Hello World"时,实际是调用了PostMessage等方法。 下面我们看看Win32消息队列的特点和限制:
WPF中的Dispatcher的主要功能类似于Win32中消息队列,当并不是替换消息循环,而是建立在消息循环之上。首先,让我们看看Dispatcher的结构: 线程相关的Dispatcher的创建当我们在一个线程上第一次创建WPF对象时,DispatcherObject构造函数会调用Dispatcher.CurrentDispatcher赋值给私有变量_dispatcher。Dispatcher.CurrentDispatcher通过Thread.CurrentThread来找到该线程对应的Dispatcher实例,如不存在则创建一个。Dispatcher的构造函数,首先创建了一个具有11个级优先级(DispatcherPriority)的优先队列PriorityQueue,这个优先队列用来保存不同优先级的操作。然后调用Win32 API RegisterClassEx注册窗口处理函数,并调用CreateWindowEx创建一个不可见的窗口。 Dispatcher有两个静态方法用来建立消息循环,定义如下:
1
public
sealed
class
Dispatcher
2 { 3 public static void Run() 4 { 5 PushFrame( new DispatcherFrame()); 6 } 7 public static void PushFrame(DispatcherFrame frame) {…} 8 } 尽管是静态方法,实际上是调用当前线程对应的Dispatcher实例的方法。Application.Run()内部实际就是调用了Dispatcher.Run(),进而调用Dispatcher.PushFrame(…)。当我们创建一个WPF项目时,VS自动生成的App.xaml,编译后的代码为
1
public
static
void
Main()
2 { 3 WpfHello.App app = new WpfHello.App(); 4 app.InitializeComponent(); 5 app.Run(); 6 } 当调用PushFrame()或Run()后,内部开始循环调用Win32 GetMessage方法,而建立消息循环。如下面的例子中,在VS缺省的WPF项目中,创建了一个新的线程,在该线程中创建一个新的WPF窗口,并启动消息循环:
1
public
partial
class
App : Application
2 { 3 protected override void OnStartup(StartupEventArgs e) 4 { 5 // Create a new thread 6 Thread monitorThread = new Thread(QueueMonitor.ThreadMain); 7 monitorThread.SetApartmentState(ApartmentState.STA); 8 monitorThread.Start( this ); 9 10 base .OnStartup(e); 11 } 12 } 13 14 class QueueMonitor 15 { 16 public static void ThreadMain(Object obj) 17 { 18 QueueMonitor monitor = new QueueMonitor(obj as Application); 19 } 20 21 private Application m_target; 22 private DispatcherFrame m_frame; 23 private MonitorWindow m_window; 24 public QueueMonitor(Application target) 25 { 26 m_target = target; 27 28 m_frame = new DispatcherFrame(); 29 m_window = new MonitorWindow(); 30 31 m_window.Closed += MonitorWindow_Closed; 32 m_window.Visibility = Visibility.Visible; 33 34 Dispatcher.Run(); 35 // Dispatcher.PushFrame(m_frame); 36 } 37 38 void MonitorWindow_Closed( object sender, EventArgs e) 39 { 40 Dispatcher.ExitAllFrames(); 41 // m_frame.Continue = false; 42 } 43 } 消息循环的控制上个例子中,我们也可以使用PushFrame方法。相比于Run(),我们可以通过PushFrame的参数DispatcherFrame对象来控制什么时候退出消息循环。如需退出当前层的循环,只需将对应的DispatcherFrame.Continue赋值false。我们可以嵌套的调用PushFrame,这点上类似Application.DoEvents,更具灵活性,但我们仍不能只要求处理特定优先级的消息。 另外可以调用Dispatcher.ExitAllFrames方法退出当前线程的所有消息循环。Dispatcher还提供一个方法DisableProcessing,该方法可以暂停消息循环处理消息。下面是这个方法的使用方法:
1
using
(Dispatcher.DisableProcessing())
2 { 3 Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, delegate { }); 4 } 该方法返回的DispatcherProcessingDisabled对象实现了IDisposable接口,在Dispose中恢复消息循环。该方法也可以嵌套的调用。 下面我们来看看如何投递消息,及Dispatcher如何处理的。 Dispatcher优先队列Dispatcher的Invoke和BeginInvoke方法,用来向Dispatcher的优先队列放置任务,前者是同步方法,后者是异步。这两个方法,第二个参数是个delegate对象,代表要执行的任务。第一个参数定义任务的优先级,具体定义可以看DispatcherPriority枚举类型。总的来说,我们可以划分为两类,Background和Foreground,另一个比较特殊的是DispatcherPriority.Send。 当在Invoke中指定Send时,且CheckAccess为真,则Invoke直接调用delegate执行任务。如果不是,则调用BeginInvoke,并等待结果,或超时。 BeginInvoke执行时,首先将该任务封装成DispatcherOperation对象,并放置在对应的优先队列中。然后判断是Background还是Foreground,如果是Foreground,则调用PostMessage往Win32消息队列中投递一条消息,然后返回。如果是Background,则检查是否有Timer,如没有则创建一个Timer,Timer会不断循环的投递消息到Win32消息队列,来触发消息的处理。当有Foreground消息是,则删掉Timer。通过这种方式,系统在空闲的时候可以处理Background消息。 当有Win32消息投递到Win32队列时,注册的窗口函数被执行,从优先队列中取出一个DispatcherOperation来执行。完成后,则投递新的Win32消息来触发下次执行,或等待Timer消息。 BeginInvoke的返回值则为DispatcherOperation对象,通过她我们可以取消,等待,或者调整该任务的优先级。在后面的系列中,我们在具体看不同Foreground优先级的使用。 优先队列钩子Hook与Win32类似,我们也可以对消息的处理添加Hook,可以添加下面的Event Handler:
1
Dispatcher.CurrentDispatcher.Hooks.OperationAborted
+=
new
DispatcherHookEventHandler(Hooks_OperationAborted);
2 Dispatcher.CurrentDispatcher.Hooks.OperationCompleted += ; 3 Dispatcher.CurrentDispatcher.Hooks.OperationPosted += ; 4 Dispatcher.CurrentDispatcher.Hooks.OperationPriorityChanged += Event Handler的参数中,我们可以获得对应的Dispatcher和DispatcherOperation对象。通过这种办法,我们可以过滤,查询或改变任务的优先级等操作。 DispatcherTimerDispatcherTimer类似WinForm中的Timer。我们可以在构造函数中指定优先级,Dispatcher实例等等。 总结总的来说,与Win32比较,如果给WPF中的线程和消息循环的机制打分的话,我觉得可以打4分的高分。WPF解决了Win32中没有优先级,跨线程调用性能的问题,友好的编程接口。如果说不足的话,如果PushFrame可以支持优先级,对Reentrancy的问题,可能能更好的控制;另外,DispatcherOperation无法获得名字,如我们要开发一个队列监视程序的话很不方便。这些我认为可以扣0.5分,那另外0.5分是什么呢? 最后,如果我们看这些类的话,全部都在Windowsbase.dll中,也就是说System.Windows.Threading中的类,如DispatcherObject,Dispatcher也可以用于其他系统中。我们甚至可以利用这些在我们的系统中。 转自:http://www.cnblogs.com/Nullnoid/ |
WPF学习与分享之二:DispatcherObject与WPF线程模型(下)
最新推荐文章于 2024-01-03 15:56:55 发布
WPF学习与分享之二:DispatcherObject与WPF线程模型(下)
2008-08-28 21:50