Spiga

一站式WPF--线程模型和Dispatcher

2009-08-31 12:13 by 周永恒, 6069 visits, 收藏, 编辑

  开始着手写这个WPF系列,这里的一站式,就是力争在每一个点上能把它讲透,当然,做不到那么尽善尽美,如果有不对的地方也欢迎朋友们指正,我会逐步补充,争取把这个系列写好。

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

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

  这两段是MSDN上关于WPF线程模型的描述。主要介绍了两个概念:一,WPF中线程一分为二,一个用于呈现(Render),一个用于管理UI;二,在UI线程中,使用了一个名为Dispatcher的类帮助UI线程处理任务。

  那么这个线程模型和Dispatcher到底是怎样的呢,它又有什么特点,有什么优缺点呢?在正式分析线程模型和Dispatcher之前,我先找一个插入点,希望这个插入点能为朋友们所理解。

  作为一个Presentation的基架,WPF的使命就是要编写图形化的操作界面。而在Windows操作系统上,图形化界面是建立在消息机制这个基础上的,那么创建一个窗口,要经历哪些步骤呢?

  1. 创建窗口类。 WNDCLASSEX wcex; RegisterClassEx(&wcex);

  2. 创建窗口。CreateWindow(…); ShowWindow(…); UpdateWindow(…);

  3. 建立消息泵。

  while (GetMessage(&msg, NULL, 0, 0))

  {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

  }

  打个比方,我们在一个自动化的厂房里生产设备。基于正规,我们会首先定义好该设备的模板,这就是创建窗口类,这里”类”更多表示类别的意思。模板定义完毕,我们可以正式生产设备了,这就是创建窗口,这个CreateWindow的时候会通过字符串来匹配到我们定义的模板(窗口类)。创建成功后,我们要让设备动起来,就要像人一样,体内一定要有类似于血液的流传机制,把命令传达到设备的各个部分,这就是消息泵,这个泵就像我们的心脏一样,源源不断的通过GetMessage并Dispatch来分发血液(消息)。既然我们通过消息来对设备下达指令,那么就要有消息队列来存储消息,在Windows中,线程为基本的调度单位,这个消息队列就在线程上,当循环使用GetMessage时,就是在当前线程的消息队列中任劳任怨的取出消息,然后分发到对应的窗口中去。

  那么具体到WPF,它又是一个怎么样的情况,如何和老的技术兼容,又有什么新的突破呢?

  WPF引入了Dispatcher的概念,这个Dispatcher的主要功能类似于Win32中的消息队列,在它的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。Dispatcher本身是一个单例模式,构造函数私有,暴露了一个静态的CurrentDispatcher方法用于获得当前线程的Dispatcher。对于线程来说,它对Dispatcher是一无所知的,Dispatcher内部维护了一个静态的List<Dispatcher> _dispatchers, 每当使用CurrentDispatcher方法时,它会在这个_dispatchers中遍历,如果没有找到,则创建一个新的Dispatcher对象,加入到_dispatchers中去。Dispatcher内部维护了一个Thread的属性,创建Dispatcher时会把当前线程赋值给这个Thread的属性,下次遍历查找的时候就使用这个字段来匹配是否在_dispatchers中已经保存了当前线程的Dispatcher。

  那么这个创建窗口,建立消息泵又是什么时候被调用的呢?在Dispatcher内部,维护了一个HwndWrapper的字段,在Dispatcher的构造函数中,调用了HwndWrapper的构造函数,这个创建窗口类,创建窗口就是在这个函数中被调用的。这里实际的类是MessageOnlyHwndWrapper,这个Message-Only,是Windows编程中常用的伎俩,创建一个隐藏窗口,仅仅用来派发消息。那么循环读取消息的消息泵又是什么时候建立起来的呢?

  Dispatcher对外提供了一个静态的Run函数,顾名思义,就是启动Dispatcher,在函数内部,调用了PushFrame函数,在这个函数中,可以找到熟悉的GetMessage, TranslateAndDispatchMessage。那么这个PushFrame是怎么回事,Frame这个概念又是如何而来的呢?

  这个就是WPF引入的一个新的概念,嵌套消息泵,就是在一个While(GetMessage(...))内部又启动了一个While(GetMessage(...))。每调用一次PushFrame,就会启动一个新的嵌套的消息泵。每调用一次GetMessage,就在线程的消息队列中取出一个消息,直至取出WM_QUIT的时候GetMessage才返回False。这个GetMessage函数Windows内部进行了处理,当消息队列为空时,挂起执行线程,避免死循环的发生。关于嵌套消息泵的优缺点,我们稍后再讲,先来看看Dispatcher是如何处理任务的:

  Windows中定义了很多Message,以WM_开头,在注册窗口类的时候需要设置窗口过程函数,GetMessage取得的消息再分发到窗口过程函数中,整个过程为: 

  

  这个图来自于侯捷的经典书籍《深入浅出MFC》,1.首先创建Window并指定窗口的过程函数WndProc。2.当窗口创建时一个WM_CREATE被放入到消息队列中,3.消息泵通过GetMessage取得该消息后分发到窗口,窗口过程函数处理这个WM_CREATE消息…

  那么WPF的Dispatcher在这个过程中扮演了什么角色呢?前面的1,2,3仍然如此,当窗口过程函数接收到消息时,它需要根据消息的类别把Windows消息转译成内部的RoutedEvent或者调用布局函数等来处理。前面提到了Dispatcher主要功能类似于Win32中的消息队列,这个队列中存放的对象是DispatcherOperation,这个DispatcherOperation,顾名思义,就是把每一个执行项封装成一个对象,类似:

  

  这个队列的类型为PriorityQueue,是一个含有优先级的队列。WPF定义了这个优先级DispatcherPriority,有

  

  当对这个PriorityQueue调用DeQueue时,就会取出优先级最高的任务。那么这个队列中的任务是什么时候被添加的,又是什么时候被取出执行的呢?

  Dispatcher暴露了两个方法,Invoke和BeginInvoke,这两个方法还有多个不同参数的重载。其中Invoke内部还是调用了BeginInvoke,一个典型的BeginInvoke参数如下:

    public DispatcherOperation BeginInvoke(Delegate method, DispatcherPriority priority, params object[] args);

  在这个BeginInvoke内部,会把执行函数method与参数args封装成DispatcherOperation,并按priority加入到PriorityQueue中,这个返回值就是内部创建的DispatcherOperation。也就是说每调用一次Invoke和BeginInvoke,就向Dispatcher中加入了一个任务,那么这个任务什么时候被执行呢?

  DispatcherPriority定义了很多优先级,WPF将这些优先级主要分成两类。前台优先级和后台优先级,其中前台包括Loaded~Send,后台包括Background~Input。剩下的几个优先级除了Invalid和Inactive都属于空闲优先级,处理顺序同后台优先级。这个前台优先级和后台优先级的分界线是以Input来区分的,这里的Input指的是键盘输入和鼠标移动、点击等等。ProrityQueue的来源有:

  

  当然,这里Hwnd级别Hook到的消息最终也是调用Dispatcher的Invoke/BeginInvoke方法加入到Dispatcher的队列中去的。当处理这个PriorityQueue时,会首先取得队列中的最大优先级,如果它属于前台优先级,执行。如果属于后台优先级,那么它要去扫描线程的消息队列,看看其中是由有类似WM_MOUSEMOVE之类的Input消息。如果没有,执行。如果存在,则放弃执行,并启动一个Timer,当Timer唤起时继续判断是否可以执行。

  那么处理PriorityQueue的时机呢?当你调用BeginInvoke,向队列中加入执行项的同时,也会调用处理Queue的判断。判断逻辑和上面类似,队列中最大优先级是前台优先级,向隐藏窗口PostMessage,这个消息是Disptcher使用RegisterWinodwMessage注册的自定义消息。然后在GetMessage的时候如果取出这个自定义消息,则处理PriorityQueue。如果是后台优先级,扫描线程消息队列的Input消息,决定是否启动Timer还是PostMessage。

  举个例子,在后台线程中向UI线程中使用Invoke来发送请求,经历的过程为:

  1. 调用Invoke,对传入的参数DispatcherPriority进行判断,如果是Send,这是个特殊的优先级,直接切换线程上下文,执行任务并返回。如果是其他的优先级,调用BeginInvoke。

  2. 在BeginInvoke中,把传入的Delegate和参数封装成DispatcherOperation,加入到PriorityQueue中。

  3. 调用队列处理的请求函数,希望处理PriorityQueue。

  4. 如果队列中最大优先级属于前台优先级,调用PostMessage向隐藏窗口发送自定义消息。后台处理这里省略不表。

  5. 在GetMessage中取得消息并分发到隐藏窗口,这里使用的是常见的SubWindow(注释一),消息通过Hook发送到Dispatcher的WndProcHook函数进行处理。

  6. 在WndProcHook中,如果接收到的Window消息是Dispatcher自定义的消息,则真正处理PriorityQueue。

  7. 处理PriorityQueue,从中取出一个任务,进行前后台优先级判断,决定是否处理还是启动Timer稍后处理。

  回过头来,说一说嵌套的消息循环,这个要从模态对话框说起,一个通常的模态对话框场景如下:

    SomeCodeA();

    bool? result = dlg.ShowDialog();

    SomeCodeB();

  代码运行在UI线程中,当执行到dlg.ShowDialog时,启动模态对话框,等待用户点击Yes/No或者关闭对话框,对话框关闭后程序继续执行SomeCodeB代码。那么程序要在SomeCodeB处等待ShowDialog返回后才继续执行。当然你可以使用WaitHandle来同步,不过这个需要挂起当前(UI)线程,如果主窗口中有动画等UI动作,那么会停止得不到响应。这里WPF使用的是PushFrame,就是在ShowDialog内部又建立起了一个消息泵。While(GetMessage(…))。一方面,可以确保UI线程中的消息可以被处理;另一方面,因为是While循环,在对话框关闭时返回,可以确保SomeCodeB的执行顺序。

  那么是不是这个嵌套的消息循环真的如此完美呢?当然不是,它打开了一扇门的同时,也打开了另一扇门。一个情景,当收到WM_SIZE消息的时候,Layout系统开始处理,如果在这个处理过程中,又启动了PushFrame,那么嵌套的消息泵就会继续从消息队列中取出消息,如果下一个消息也是WM_SIZE,那么进行处理。假设这个消息处理结束后这个嵌套的消息泵返回了,那么第一个WM_SIZE得以继续处理。这样就发生了错误,本来12的处理顺序变成了121。当然这种情况不仅仅发生在Layout中,所以WPF在Dispatcher中加入了一个DisableProcessing函数,在Layout等关键过程中调用了这个函数,在这个过程中停止pump消息和禁止PushFrame。

  在WPF中,所有的WPF对象都派生自DispatcherObject,DispatcherObject暴露了Dispatcher属性用来取得创建对象线程对应的Dispatcher。鉴于线程亲缘性,DispatcherObject对象只能被创建它的线程所访问,其他线程修改DispatcherObject需要取得对应的Dispatcher,调用Invoke或者BeginInvoke来投入任务。一个UI线程至少有一个Dispatcher来建立消息泵处理任务,一个Dispatcher只能对应一个UI线程。那么UI线程和Render线程又如何呢?

  开篇提到,WPF线程一分为二,一个是UI线程,一个是Render线程。这两个被设计成分离的关系,通过Channel(event)来进行通信。两者之间的数量关系是一个WPF进程只能有一个Render线程,旦可以有大于等于一个的UI线程。通常情况下是一个UI线程,也就是一个Dispatcher,那么什么情况下需要建立多个呢?

  大多情况下是不需要的,少数情况下,比如MediaElement,或者Host其他ActiveX控件,我们期望在其他线程中创建,以提高性能。可以新建线程,在新线程中创建控件,并调用Dispatcher.Run启动Dispatcher。这样主Window和新控件就处在不同线程中,两者间的通信可以使用VisualTarget连接视觉树或者使用D3DImage拷贝新控件到主Window中显示。

  开篇有益,WPF没有什么全新的技术,但提出了很多新的概念。就像施了妆包装后的美人,远看很美,可是风一来手一伸,丫的,不过如此。^_^


注释一:  SubWindow,子窗口子类化。通常情况,所有同类别Window会共用同一个消息处理函数WndProc,子Window可以调用SetWindowLong用SubWndProc替换WndProc,这个通常称为Sub-Window。


作者:周永恒 
出处:http://www.cnblogs.com/Zhouyongh  
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Add your comment

35 条回复

  1. #1楼 Breeze Woo      2009-08-31 12:46
    先抢沙发,然后慢慢看!
     回复 引用 查看   
  2. #2楼 DiryBoy      2009-08-31 12:57
    板凳~~
     回复 引用 查看   
  3. #3楼 黄泉天上来      2009-08-31 13:54
    不错 又学了不少东西 lz继续啊
     回复 引用 查看   
  4. #4楼 Franz      2009-08-31 14:26
    你是通过什么方式来证明你的观点的呢? 比如WPF的消息机制!
     回复 引用 查看   
  5. #5楼 谁起了我的名字      2009-08-31 14:36
    正在学习WPF,先顶一下。
     回复 引用 查看   
  6. #6楼[楼主] 周永恒      2009-08-31 14:36
    @Franz
    关于这部分内容,主要是通过Reflector查看的,写文章前,我也用Windbg简单跟了一下这个消息过程。当然,如果有什么不对的地方也欢迎你指正。
     回复 引用 查看   
  7. #7楼 Franz      2009-08-31 14:56
    @周永恒
    写的挺好的.
    其实更应该把你的研究过程拿出来分享一下.这才是授人于渔!
     回复 引用 查看   
  8. #8楼[楼主] 周永恒      2009-08-31 15:49
    @Franz
    呵呵,其实这些内容也是这几年用WPF慢慢看来的。有点感悟了,总结一下。没把它写成观察调用堆栈,用Debug来跟踪的文章,一方面,时间久远了,很多细节当时调试跟踪了,这次只是简单验证一下想法是否正确。另一方面,也是最重要的,就像你说的要授人以渔一样,我觉得最重要的是要帮大家理清一下主线,把它讲懂,这也是我写作的思路,太重于细节容易让别人看起来疲劳,抓不住中心。具体的细节还要自己研究,自己搞一下才能真正理解。如果对Debug的过程感兴趣,呵呵,那以后尽量了。
     回复 引用 查看   
  9. #9楼 蓝蓝的天      2009-08-31 18:29
    慢慢跟进
     回复 引用 查看   
  10. #10楼 Curry      2009-08-31 19:05
    楼主说的实在太好了,能不能再说 System.Windows.Threading.DispatcherPriority中各个设置的引用时机点和注意点。
     回复 引用 查看   
  11. #11楼 yoyo圈      2009-08-31 21:35
    说实话,功力有点浅,看这篇文章确实有点不好理解,一不好理解看得就快了,呵呵。只能快点跟进了,我非常喜欢楼主这种严谨和细致的精神,做技术的且不说技术水平有多高,假如没有一个好的态度,估计做起项目来就会要多玄就有多玄了。
     回复 引用 查看   
  12. #12楼 道法自然      2009-08-31 23:01
    该技术说的有点抽象,没有用浅显易懂语言表述,其实在WinForm时代,每一个Form就有Invoke和BeginInvoke的。在这里,我补充一点:
    (1)什么是呈现线程和UI线程:在我理解中,呈现线程仅是用于绘画窗口界面元素,每一个绘画都是由UI线程触发的,UI线程向消息队列发送绘画相关消息,由呈现线程实现绘画,比如重绘消息。
    (2)在可视化应用中,如果,没有新建线程,那么所有代码都是由UI线程执行的,因此,在执行一个大任务时,由于UI线程忙,无法发送重绘消息,窗口就不会有响应,好像死掉一样。
    (3)对UI的操作,比如更改控件属性(引起重绘),都必须由UI线程来执行,其它线程来更改UI的信息的话,会抛出异常。
    (4)为了避免执行大任务导致窗口没有反应,你需要新建一个线程,让它执行任务,当新线程需要操作UI时,它需要利用Invoke和BeginInvoke来操作,这两个函数最终转给UI线程执行。
     回复 引用 查看   
  13. #13楼 Curry      2009-09-01 08:47
    @道法自然
    哈哈,很好的补充,不过估计作者以为大家都知道这个前提,就直接说明Invoke和BeginInvoke内部具体怎么做的了。
     回复 引用 查看   
  14. #14楼[楼主] 周永恒      2009-09-01 10:56
    @道法自然

    关于Invoke和BeginInvoke,WPF和Winform遵守的都是SynchronizationContext,它们的线程间通信用的都是这个模型,当然,处理也类似。

    关于UI线程和Render线程,作为一个retained mode的绘制系统,WPF引入了Composite的概念,并不仅仅在窗口变为无效时通过WM_PAINT来绘制。这部分打算之后再详细的挖一挖。

    你补充的经验很好,这篇文章可能铺垫的少一些,主要是关于UI线程和Background线程方面的东西已经被人说烂了,跟文章的主线也不是很搭,就直接略掉了。
    谢谢补充,hoho
     回复 引用 查看   
  15. #15楼[楼主] 周永恒      2009-09-01 11:00
    @Curry
    呵呵,谢谢关注。关于DispatcherPriority,它的问题主要是WPF系统内部是怎么使用这个优先级的,我没有详细调查过,不过还有一些经验,我会补充一下的。
     回复 引用 查看   
  16. #16楼 helloj2ee      2009-10-15 18:29
    这篇文章显示了楼主的功力深厚。经Curry介绍 看了此文。非常受启发。在我调试源码的时候 给了很多帮助。
     回复 引用 查看   
  17. #17楼 helloj2ee      2009-10-15 21:17
    还是有问题 虽然能够在Dispatcher看到WndProc函数,但是找不到在哪个地方 将这些消息封装成routed event?
     回复 引用 查看   
  18. #18楼[楼主] 周永恒      2009-10-16 11:24
    @helloj2ee
    将消息封装成Routed Event是从Window开始的。

    比如你在Window上点击,应该要得到一个MouseDownEvent.WPF的Window通过HwndSource来封装隐藏的Win32Window。

    Window消息虽多,WPF将它分为了几类,分别用不同的Manager来处理这些消息--调用函数或者将消息转化为Routed Event等等,你要找的转化过程就在这里。

    比如说Input消息,WPF是用InputManager来处理的。你可以用Reflecter来查看HwndSource的源码,它在窗口消息处理中加入了Hook,有LayoutHook, InputHook, HwndTargetHook等, 分别对应Resize; Input; Paint。

    InputManager接收到消息后,内部压栈,再经过处理,找到对应的DependencyObject,如果需要(并且是UIElement),则RaiseEvent.
     回复 引用 查看   
  19. #19楼 天天不在      2010-04-21 09:19
    先大致看了下.还以为在看Win32编程了.
     回复 引用 查看   
  20. #20楼 ibrahim      2010-07-18 11:51
    @周永恒
    博主关于“嵌套消息泵”的论述不是很理解,另外“这个Message-Only,是Windows编程中常用的伎俩,创建一个隐藏窗口,仅仅用来派发消息。那么循环读取消息的消息泵又是什么时候建立起来的呢”也不是很理解,

    能否提供相应代码解释一下?

    谢谢!
     回复 引用 查看   
  21. #21楼[楼主] 周永恒      2010-07-19 11:06
    @ibrahim
    嵌套消息泵,类似于
    While(GetMessage(...))
    {
    While(GetMessage(...))
    {
    ...
    }
    }

    使用隐藏窗口发消息是Window常用的技巧,发送消息要指定窗口的句柄,Dispatcher创建了一个隐藏窗口,在BeginInvoke中把执行任务封装成对象并向这个隐藏窗口PostMessage, 这样BeginInvoke方法可以迅速返回,并且在GetMessage的时候可以用通用的Priority来处理执行任务。
     回复 引用 查看   
  22. #22楼 ibrahim      2010-07-19 23:20
    @周永恒
    有个问题我一直搞不懂,举个例子,我创建了一个WPF窗口(window),那么这个窗口是系统在一个UI线程中创建
    的,它底层对应于win32的一个window。如果这时候使用CurrentDispatcher时,它会创建一个新的Dispatcher对象
    ,而创建Dispatcher对象,则会创建一个隐藏窗口。

    如果以上我说的是对的,那么此时这个UI线程中,存在2个窗口,一个是WPF根节点窗口,一个是访问CurrentDispatcher而创建的隐藏窗口。

    问题如下:

    1-如果不使用CurrentDispatcher,那么不会创建这个隐藏窗口,这种说法对吗?

    2-这个隐藏窗口有什么用,不是已经有一个窗口了吗?

    3-一个UI线程存在2个窗口,这两个窗口是否共用一个消息泵,也就是你所说的嵌套消息泵?还是各用各的消息泵?

    4-这两个窗口是否共用一个wndproc,也就是这个wndproc是Dispatcher的wndproc?

    5-是否是Dispatcher把发送给WPF根窗口消息也hook到Dispatcher的wndproc来处理?这样意义何在,干吗不交给根窗口的wndproc去处理?

    6-如果是发给WPF根窗口的鼠标消息,Dispatcher的wndproc如何处理?
     回复 引用 查看   
  23. #23楼[楼主] 周永恒      2010-07-20 15:17
    @ibrahim

    你的这些问题,要从为什么要有Dispatcher开始讲:

    我提到过,Dispatcher相对于Win32中的消息队列,它的核心是它内部包含一个 PriorityQueue<DispatcherOperation> _queue,这是一个有优先级的队列,那么剩下的问题是,这个DispathcerOperation(DO)从哪里来,什么时候被执行,它又怎么相对于Win32中的消息队列呢?

    1. 创建一个Window,点击它,消息肯定会发送到这个Window的WndProc处理,不做额外的事情,和Dispatcher一点关系都没有。这个额外的事情就是我前面提到的SubWindow,它子类化了WndProc,然后不是由默认的DefWndProc来处理Window消息,而是把消息包装成了DO,然后调用Dispatcher的Invoke方法传入这个DO。

    2. 一个UI线程可能会创建很多Window,这些Window都会把它的消息包装成DO然后调用Dispatcher的Invoke方法,这就给了Dispatcher可以控制的可能。

    3. Dispatcher中的PushFrame是启动一个消息泵(GetMessage(...)),当然这个消息泵一个线程只能有一个,但是可以嵌套。

    4. 举个例子,在Window上用鼠标点击,系统把一个鼠标消息加入到线程消息队列,Dispatcher的GetMessage循环拿到这个消息,然后TranslateAndDispatch到该Window。
    Window的WndProc得到该消息,封装成DO并且调用Dispatcher的Invoke方法。DO开始执行,用try-catch执行,执行的方法是一个Hook数组。
    WPF的Window内部含有一个HwndSource,这个HwndSource实现了一些通用的Hook逻辑,LayoutHook,InputHook等等,你可以调用HwndSource.AddHook加入自己的Hook。

    5. Dispatcher定义的是一套通用的模型,一个Queue<DO>, 可以调用Invoke或者BeginInvoke向Queue中加入DO。 那么这个DO怎么执行呢?

    就需要用到Dispatcher创建的隐藏Window了,Dispatcher在PushFrame后,该线程就进入到一个GetMessage循环中。那么其他线程调用了BeginInvoke方法加入了一个DO,Dispatcher需要去执行它。这时它就要给自己的隐藏窗口发送消息,消息是它调用RegisterWindowMessage注册的的一个Window消息(DispatcherProcessQueue)。
    当然,GetMessage循环仍然在运行,当它获取到消息是DispatcherProcessQueue时,它就知道要处理Queue中的DO了。

    7, Dispatcher设置了一个Priority,当级别是Send时候,直接执行,不把这个DO加入到队列中去,默认情况下,由窗口subclass过来传入方法都是直接执行的。

    因为概念比较多,这篇文章讲的太粗了,以后有时间细细的讲一下这个过程。
     回复 引用 查看   
  24. #24楼 ibrahim      2010-07-20 19:05
    @周永恒
    谢谢你的解答。

    Dispatcher中的PushFrame(也就是Dispatcher中的Run方法)是什么时候?被谁调用的?
    我感觉PushFrame很诡异,在第一个window建立时,消息泵(当然是无嵌套的)早已确立,那么Dispatcher是如何在其中又加上一个GetMessage循环的?

    这个嵌套的消息泵是像下面这样写的吗?
    while(GetMessage()) //UI线程第一个window建立时的消息泵
    {
    TranslateMessage();
    DispatchMessage();
    while(GetMessage()) //Dispatcher中的PushFrame调用后嵌入的
    {
    TranslateAndDispatchMessage();
    }

    }
    UI线程第一个window被subclass后的proc方法是否如下这样写?

    subWndProc()
    {
    Invoke();
    ProcessPriorityQueue();//处理ProrityQueue;
    }

    Invoke()
    {
    if(message.priority==send)
    {
    execute(message);//立即执行
    }
    else
    {
    BeginInvoke();
    }
    }

    BeginInvoke()
    {
    DispatcherOperation do=new DispatcherOperation();//用message构造DispatcherOperation;
    PriorityQueue.add(do);//加入到PriorityQueue中
    }

    ProcessPriorityQueue()
    {
    如果存在,则放弃执行,并启动一个Timer,当Timer唤起时继续判断是否可以执行。
    DispatcherOperation do=GetTopPriorityOperation();//当处理这个PriorityQueue时,会首先取得队列中的最大优先级;
    if(do.priority==前台优先级) //如果它属于前台优先级,向隐藏窗口post一个message
    {
    PostMessage(do,隐藏窗口);
    }
    if(do.priority==后台优先级) //如果它属于后台优先级,那么它要去扫描PriorityQueue,看看其中是由有类似WM_MOUSEMOVE之类的Input消息。
    {
    if(NotExistInputMessage()) //如果PriorityQueue不存在Input消息
    {
    PostMessage(do,隐藏窗口);
    }
    else
    {
    Timer timer=new Timer(); //如果存在,则放弃执行,并启动一个Timer,当Timer唤起时继续判断是否可以执行。
    }
    }

    }

    你所提到的“DO开始执行,用try-catch执行,执行的方法是一个Hook数组”什么意思?UI线程第一个window被subclass后的proc方法与隐藏窗口的proc方法不是同一个方法吗?

    文中多次提到的Timer有什么用?
     回复 引用 查看   
  25. #25楼[楼主] 周永恒      2010-07-21 19:29
    @ibrahim

    不好意思,这两天有点忙。WPF的程序也是从Main函数里启动的,main函数中会创建Application,在Application启动时就会调用到Dispatcher。Dispatcher开始创建,至于后续创建的主Window等window,都是在Dispatcher中,这个UI线程中唯一的消息泵也是在Dispatcher中创建的,并不是那种多余的感觉。

    具体的我改天再写一篇文章来描述一下吧,呵呵
     回复 引用 查看   
  26. #26楼 ibrahim      2010-07-23 11:01
    @周永恒
    期待中,呵呵
     回复 引用 查看   
  27. #27楼 Curry      2010-07-28 11:43
    不知道我这样理解对不,MessageOnly的窗体只是为了Dispatcher中的BeginInvoke用的。

    这样就有两个问题
    1.为什么BeginInvoke需要MessageOnly的窗体?
    答:BeginInvoke需要异步执行,也就是不需要马上执行,而WPF是线程安全的,也就是操作只能在同一线程中执行,那么就需要把现在的操作缓存一段时间之后执行。用PostMessage就可以达到这个效果(把消息发到消息队列中缓存)。PostMessage只是发送消息,所以需要一个消息泵来接收这个消息,要接收消息泵在Windows系统机制下必须要有窗体,但是这样的窗体不需要UI,所以用了MessageOnly。

    2.为什么要MessageOnly的消息泵,而不用窗体的那个消息泵?
    答:为了分层,使得代码结构更清晰,外层的消息泵主要用来接受鼠标,键盘,重绘等消息,MessageOnly的窗体只用以处理BeginInvoke的异步信息;也为了简便线程的判断;另外如果有在外面不同线程新建一个窗体那么使用也是统一的。
     回复 引用 查看   
  28. #28楼 leaf j      2011-02-11 12:30
    WPF线程一分为二,一个是UI线程,一个是Render线程。这两个被设计成分离的关系,通过Channel(event)来进行通信。两者之间的数量关系是一个WPF进程只能有一个Render线程,旦可以有大于等于一个的UI线程。通常情况下是一个UI线程,也就是一个Dispatcher
     回复 引用 查看   
  29. #29楼 顶你[未注册用户]2011-02-27 16:08
    @道法自然

    精辟!, 读了很多人的文章,都没有言简意赅的给出解释。你的回复是我茅塞顿开!
     回复 引用   
  30. #30楼 虚心学习的猫      2011-03-13 13:46
    我怎么感觉一头雾水啊,是不是我太水了。。
     回复 引用 查看   
  31. #31楼 天天宝贝的妈咪      2011-04-12 11:45
    昨天研究了一天,不如看一下楼主的这篇文章,惊叹,并有感于自己的差距之远,继续学习进步!同时真心感谢@道法自然,总结的几点受益匪浅,谢谢,谢谢!

     回复 引用 查看   
  32. #32楼 一杯苦咖啡      2011-06-13 13:46
    mark
     回复 引用 查看   
  33. #33楼 初始小花      2011-08-03 11:19
    对于这样的东西,很多人无法学会“渔”,还是给“鱼”吧,希望能多写点应用。
     回复 引用 查看   
  34. #34楼 猫爪洗脸君      2011-12-27 10:57
    更正一点,List<Dispatcher> _dispatchers 不正确,应该是 List<WeakReference> _dispatchers。
     回复 引用 查看   
  35. #35楼 唯自我      2012-01-06 14:12
    mark
     回复 引用 查看   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1557126 9siGrnQr1hE=