WPF与WinForm对比多线程编程优化是关键

IT168技术】很多人问过我这样一个问题:WPF和以前的WinForm有什么区别?

  我之前的回答一直是:没什么区别,仅仅是表示层用XAML封了层皮,使得Windows看起来更炫了。

  今天(确切的说是昨天),我终于发现了我肤浅。首先我要澄清一下,WPF较之WinForm的先进之处不止一点点。

  对于WPF,很多人都以为这是微软的一个小玩具,充其量就是让Vista和Win7的表示层更炫了,然后就会吸引人们去购买——当然了,我相信多数人购买Wim7的动机并不在乎它的内核做了什么变动...

  言归正传...

   WPF将Windows表示层发展至用声明式语言进行开发,并且融入大量的动画和特效,使得在Win32中极难做到的富客户端应用能在WPF中信手拈 来,同时用矢量图取代位图,引入路由事件(RoutedEvent)对元素树进行多层监听,通过依赖属性(DependencyProperty)动态变 更控件树等,这些都是表面上我们所看到的。不过这些不是我今天想说的重点,何况有不少牛人的研究要比我深入多了,实在是自惭形秽啊,今天我只是承认错误来 的。

  在众多WPF对于WinForm的优势中,令我意识到我之前的错误的一点就是WPF对于多线程编程的优化。

  在 WinForm程序开发时,一旦涉及多线程操作,我们一般不可能没有见过InvalidOperationException这个异常。这个异常的出现多 数情况是由于worker线程(子线程)修改主线程控件(或对象)的属性而导致的非法操作,当然这种做法也非每次都会失败,这主要取决于子线程想要操作的 对象是不是线程安全的。

   无论是Windows窗体还是WPF,问题的成因都很简单:Windows控件使用的是组件对象模型(Component Object Model,COM)单线程单元(Single-threaded Apartment,STA)模型,因为其底层的控件是单元线程(apartment-threaded)的。此外,很多控件都用消息泵(message pump)来完成操作。因此,这种模型就需要所有调用该控件的方法都和创建该控件的方法位于同一个线程上。

  WinForm控件提供了 InvokeRequired属性来判断当前线程是不是创建此控件的线程。一旦控件创建完成,那么InvokeRequired的效率将会不错,且也能保 证安全。不过若是目标控件尚未被创建(此时,虽然C#对象已经存在,不过其底层的窗口句柄仍旧为null),那么InvokeRequired则可能会耗 费比较长的时间。(它会从下至上遍历整个控件树,直到找到一个可以承载此控件且已经实例化并被创建了的父控件,这种做法可以保证子控件将会与父控件在同一 个线程上创建。找到合适的父控件之后,框架即可执行同样的检查,比较当前线程的ID和创建该父控件的线程的ID。)。若是框架无法找到任何一个已创建的父 窗体,那么则需要找到一些其他类型的窗体。若在层次体系中无法找到可用的窗体,那么框架将开始寻找暂存窗体(parking window),暂存窗体让你不会被某些Win32 API奇怪的行为所干扰。简而言之,有些对窗体的修改(例如修改某些样式)需要销毁并重新创建该窗体。暂存窗体就是用来在父窗体被销毁并重新创建的过程中 用来临时保存其中的控件的。在这段时间内,UI线程仅运行于暂存窗体中。

  “通常,WPF 应用程序从两个线程开始:一个用于处理呈现,一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕以及运行应用程序代码。”

   “UI 线程对一个名为 Dispatcher 的对象内的工作项进行排队。 Dispatcher 基于优先级选择工作项,并运行每一个工作项,直到完成。每个 UI 线程都必须至少有一个 Dispatcher,并且每个 Dispatcher 都只能在一个线程中执行工作项。”

  ——MSDN

  WPF开始设计的时候,就将多线程的问题考虑了进去,上述很多过程都 得到了简化。这得益于Dispatcher类的使用,每个线程都有一个Dispatcher。在第一次访问某个控件的Dispatcher时,类库将察看 该线程是否已经拥有了Dispatcher。若已经存在,那么直接返回。如果没有的话,那么将创建一个新的Dispatcher对象,并关联在控件及其所 在的线程之上。Dispatcher提供了类似InvokeRequired的方法(CheckAccess)【实际上并不提倡使用此方法,且在目前的 WPF中此方法已经被取消】。这个方法只是比较线程的ID,所以会很快。

  另外,Dispatcher提供了优先队列(总共11个 Priority,主要是用于WPF中UI的层次结构设计,比如动画的优先级就是最高的);异步调用(WinForm中的 Control.BeginInvoke和Control.EndInvoke也提供异步功能);DispatcherTimer (使用 DispatcherTimer 而不是使用 System.Timers.Timer 的原因是 DispatcherTimer 与 Dispatcher 运行于相同的线程,并且可以在 DispatcherTimer 上设置 DispatcherPriority),简化了开发多线程GUI程序

你可能感兴趣的:(WinForm)