深入WPF -- Dispatcher(补)

新建一个Window程序,代码如下:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff"><span style="color:#0000ff">int</span> WINAPI _tWinMain(HINSTANCE hInstance,  HINSTANCE hPrevInstance, LPTSTR lpCmdLine,  <span style="color:#0000ff">int</span> nCmdShow) { <span style="color:#0000ff">RegisterWindowClass</span>(hInstance);     <span style="color:#008000">//1 </span> HWND hWnd = <span style="color:#800080">CreateWindow</span>(<span style="color:#ff0000">szWindowClass</span>, szTitle, WS_OVERLAPPEDWINDOW, <span style="color:#008000">//2</span> CW_USEDEFAULT, 0, CW_USEDEFAULT,z 0, NULL, NULL, hInstance, NULL); <span style="color:#800080">ShowWindow</span>(hWnd, nCmdShow); MSG msg; <span style="color:#008000">//3</span> <span style="color:#0000ff">while</span> (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } <span style="color:#0000ff">return</span> (<span style="color:#0000ff">int</span>) msg.wParam; }</span></span></span></span> |

  其中的RegisterWindowClass

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff">WORD RegisterWindowClass(HINSTANCE hInstance) { WNDCLASSEX wcex;</span></span></span></span> csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff"> wcex.lpszClassName = <span style="color:#ff0000">szWindowClass</span>;</span></span></span></span> csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff"> wcex.lpfnWndProc = <span style="color:#800080">WndProc</span>; ... } LRESULT CALLBACK <span style="color:#800080">WndProc</span>(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { <span style="color:#0000ff">switch</span> (message) { <span style="color:#0000ff">case</span> WM_PAINT: ... }</span></span></span></span> |

  这个创建窗口并显示的过程如下:

  1. 调用RegisterWindowClass注册窗口类,关联其中的窗口过程WndProc。
  2. 调用CreateWindow创建窗口并显示。
  3. (主线程)进入GetMessage循环,取得消息后调用DispatchMessage分发消息。

  这里的GetMessage循环就是所谓的消息泵,它像水泵一样源源不断的从线程的消息队列中取得消息,然后调用DispatchMessage把消息分发到各个窗口,交给窗口的WndProc去处理。

  用一副图来表示这个过程:

  1. 鼠标点击。
  2. 操作系统底层获知这次点击动作,根据点击位置遍历找到对应的Hwnd,构建一个Window消息MSG,把这个消息加入到创建该Hwnd线程的消息队列中去。
  3. 应用程序主线程处于GetMessage循环中,每次调用GetMessage获取一个消息,如果线程的消息队列为空,则线程会被挂起,直到线程消息队列存在消息线程会被重新激活。
  4. 调用DispatchMessage分发消息MSG,MSG持有一个Hwnd的字段,指明了消息应该发往的Hwnd,操作系统在第2步构建MSG时会设置这个值。
  5. 消息被发往Hwnd,操作系统回调该Hwnd对应的窗口过程WndProc,由WndProc来处理这个消息。

  这是一个简略的Window消息处理流程,往具体说这个故事会很长,让我们把目光收回到WPF,看看WPF和即将介绍的Dispatcher在这个基础上都做了些什么,又有哪些出彩的地方。

仍然从Main函数说起

  作为应用程序的入口点,我们仍然从Main函数走进WPF。

  新建一个WPF工程,如下:

  默认的WPF工程中中是找不到传统的Program.cs文件的,它的App.xaml文件的编译动作为ApplicationDefinition,编译后,编译器会自动生成App.g.cs文件,包含了Main函数。如下:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff"> [System.STAThreadAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()] <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> Main() </span></span></span></span> csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff">    { WpfApplication3.App app = <span style="color:#0000ff">new</span> WpfApplication3.App(); app.InitializeComponent(); app.Run(); }</span></span></span></span> |

  这里出现了Application类,按MSDN上的解释,"Application 是一个类,其中封装了 WPF 应用程序特有的功能,包括:应用程序生存期;应用程序范围的窗口、属性和资源管理;命令行参数和退出代码处理;导航"等。

  调用app.Run()之后,按照前面Win32的步骤,应用程序应进入到一个GetMessage的消息泵之中,那么对WPF程序来说,这个消息泵是什么样的呢?又和Dispatcher有什么关系呢?

走进Dispatcher

  Dispatcher的构造函数是私有的,调用Dispacher.CurrentDispatcher会获得当前线程的Dispatcher,Dispatcher内部持有一个静态的所有Dispatcher的List。因为构造函数私有,只能调用CurrentDispatcher来获得Dispatcher,可以保证对同一个线程,只能创建一个Dispatcher。

  Dispatcher提供了一个Run函数,来启动消息泵,内部的核心代码是我们所熟悉的,如:

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| csharpcode <span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#000000"><span style="background-color:#ffffff"> <span style="color:#0000ff">while</span> (frame.Continue) { <span style="color:#0000ff">if</span> (!GetMessage(<span style="color:#0000ff">ref</span> msg, IntPtr.Zero, 0, 0)) <span style="color:#0000ff">break</span>; TranslateAndDispatchMessage(<span style="color:#0000ff">ref</span> msg); }</span></span></span></span> |

  这里出现了一个Frame的概念,暂且不谈,来看看Dispatcher相对于传统的消息循环,有哪些改进的地方。

Dispatcher的新意

  在Winform的消息循环中,

  1. 为了线程安全,调用Control的Invoke或者BeginInvoke方法可以在创建控件的线程上执行委托,方法的返回值分别为object和IAsyncResult。尽管可以使用IAsyncResult的IsCompleted和AsyncWaitHandle等方法来轮询或者等待委托的执行,但对于对任务的控制来讲,这个粒度是不够的,我们不能取消(Cancel)一个已经调用BeginInvoke的委托任务,也不能更换两个BeginInvoke的执行顺序。
  2. 更为友好的接口支持,Windows编程中,在窗口消息循环中加入Hook是常见的需求,Dispatcher提供了DispatcherHooks类,以Event的形式对外提供了OperationAborted,OperationCompleted,OperationPosted等事件。

  这里的Operation指的是DispatcherOperation,为了更好的控制消息循环,WPF引入了DispatcherOperation来封装Window消息,这个DispatcherOperation如下:

DispatcherOpration

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public sealed class DispatcherOperation { public Dispatcher Dispatcher { get; } public DispatcherPriority Priority { get; set; } public object Result { get; } public DispatcherOperationStatus Status { get; } public event EventHandler Aborted; public event EventHandler Completed; public bool Abort(); public DispatcherOperationStatus Wait(); public DispatcherOperationStatus Wait(TimeSpan timeout); } |

  DispatcherOperation类看起来还是比较简单明了的,以属性的形式暴露了Result(结果),Status(状态),以及用事件来指出这个Operation何时结束或者取消。其中比较有意思的是Priority属性,从字面来看,它表示了DispatcherOperation的优先级,而且提供了get和set方法,也就是说,这个DispatcherOperation是可以在运行时更改优先级的。那么这个优先级是怎么回事,Dispatcher又是如何处理DispatcherOperation的呢,让我们深入DispatcherOperation,来看看它是如何被处理的。

深入DispatcherOperation(DO)

  所谓深入,也要有的放矢,从三个方面来谈一下DispatcherOperation:

  1. DispatcherOperation是如何被创建的。
  2. DispatcherOperation是何时被执行的。
  3. DispatcherOperation是怎样被执行的。

  Dispatcher提供了BeginInvoke和Invoke两个方法,其中BeginInvoke的返回值是DispatcherOperation,Invoke函数的内部调用了BeginInvoke,也就是说,DispatcherOperation就是在这两个函数中被创建出来的。我们可以调用这两个函数创建新的DO,WPF内部也调用了这两个函数,把Window消息转化为DispatcherOperation,用一副图表示如下:

相关推荐
dalong1016 小时前
WPF:DataGrid样式
wpf
samble2 天前
从零搭建工业控制系统(二十二):线程安全与并发控制
c#·wpf·并发·mvvm·线程安全·工业控制
姜穆澜3 天前
OneID 从 0 到 1 完整生产案例(三)
大数据·wpf
dalong103 天前
WPF:MVVM 示例
大数据·wpf
samble3 天前
从零搭建工业控制系统(二十一):状态管理系统——中心状态对象设计
c#·wpf·mvvm·状态管理·工业控制
mengge.cloud3 天前
存储技术基础小白教程
linux·运维·服务器·wpf·存储
DreamLife☼4 天前
复杂Agent系统架构设计
系统架构·wpf·agent·组件·设计·构架·说明
SamChan904 天前
PDF翻译服务端到端基准测试:4款主流方案的吞吐量、延迟、内存占用对比
服务器·网络·python·ai·pdf·wpf
bugcome_com4 天前
Avalonia 控件模板实战:从 WPF 迁移自定义 Button 样式的完整指南
wpf
七夜zippoe4 天前
DolphinDB 故障排查实战:系统、数据库与集群常见问题诊断
数据库·wpf·集群·常见问题·故障排查·dolphindb