我的编程空间,编程开发者的网络收藏夹
学习永远不晚

WPF线程模型和Dispatcher怎么用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

WPF线程模型和Dispatcher怎么用

这篇文章将为大家详细讲解有关WPF线程模型和Dispatcher怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

WPF线程模型是从WPF的两个线程:一个用于处理呈现和一个用于管理UI开始。并展开同时讨论Dispatcher的相关对象。

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

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

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

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

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

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

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

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

建立消息泵。  

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_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取得的消息再分发到窗口过程函数中,整个过程为: 

WPF线程模型和Dispatcher怎么用

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

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

WPF线程模型和Dispatcher怎么用

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

WPF线程模型和Dispatcher怎么用

当对这个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的来源有:

WPF线程模型和Dispatcher怎么用

当然,这里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来发送请求,经历的过程为:

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

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

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

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

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

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

处理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。

关于“WPF线程模型和Dispatcher怎么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

WPF线程模型和Dispatcher怎么用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

WPF线程模型和Dispatcher怎么用

这篇文章将为大家详细讲解有关WPF线程模型和Dispatcher怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。WPF线程模型是从WPF的两个线程:一个用于处理呈现和一个用于管理UI开始。并展开同时讨
2023-06-17

redis怎么用单线程模型

redis 单线程模型Redis 是一个开源的内存数据库,它以其高性能和灵活性而闻名。一个独特的特性是它采用单线程模型。单线程模型的优势单线程模型的主要优势在于:高吞吐量:由于没有线程上下文切换的开销,单线程可以处理大量的请求。低延迟
redis怎么用单线程模型
2024-05-21

在WPF中怎么使用多线程更新UI

本篇内容主要讲解“在WPF中怎么使用多线程更新UI”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“在WPF中怎么使用多线程更新UI”吧!有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会
2023-07-02

Redis的单线程模型怎么保证高性能

Redis的单线程模型通过以下几种方式保证高性能:非阻塞I/O:Redis使用非阻塞I/O模型,可以在一个线程中同时处理多个客户端请求,减少了线程切换的开销,提高了性能。事件驱动机制:Redis使用事件驱动机制来处理客户端请求,通过事件循环
Redis的单线程模型怎么保证高性能
2024-05-07

SpringBoot线程池和Java线程池怎么使用

这篇文章主要介绍“SpringBoot线程池和Java线程池怎么使用”,在日常操作中,相信很多人在SpringBoot线程池和Java线程池怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SpringB
2023-07-06

Python多线程中Queue模块怎么用

这篇文章将为大家详细讲解有关Python多线程中Queue模块怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。queue介绍queue是python中的标准库,俗称队列,可以直接import 引用,在
2023-06-20

Java的Future多线程模式怎么使用

本篇内容介绍了“Java的Future多线程模式怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在Java5后,提供了大量处理多线程的
2023-06-17

C#中单例模式与多线程怎么用

这篇文章给大家分享的是有关C#中单例模式与多线程怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、单例模式我们先来看看两种创建单例模式的示例代码。1、饿汉式 饿汉式创建单例模式是在程序里面直接初始化了一个对
2023-06-29

Java多线程中Future设计模式怎么用

这篇文章将为大家详细讲解有关Java多线程中Future设计模式怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Future -> 代表的是未来的一个凭据public interface Future
2023-06-25

Laravel应用程序中怎么使用模型工厂

本文小编为大家详细介绍“Laravel应用程序中怎么使用模型工厂”,内容详细,步骤清晰,细节处理妥当,希望这篇“Laravel应用程序中怎么使用模型工厂”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Laravel
2023-07-04

java单例模式和线程安全问题怎么解决

这篇文章主要介绍“java单例模式和线程安全问题怎么解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java单例模式和线程安全问题怎么解决”文章能帮助大家解决问题。单例模式、多实例模式、和线程安全
2023-07-05

spring scheduled单线程和多线程使用的坑怎么解决

本篇内容介绍了“spring scheduled单线程和多线程使用的坑怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!公司在使用定时任
2023-06-29

Java多线程和IO流怎么应用

这篇文章主要介绍“Java多线程和IO流怎么应用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java多线程和IO流怎么应用”文章能帮助大家解决问题。Java多线程和流的应用最近看到了一个例子,是使
2023-07-06

C#多线程锁lock和Monitor怎么用

本文小编为大家详细介绍“C#多线程锁lock和Monitor怎么用”,内容详细,步骤清晰,细节处理妥当,希望这篇“C#多线程锁lock和Monitor怎么用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1,Loc
2023-06-29

怎么理解java高并发的用户线程和守护线程

这篇文章主要讲解了“怎么理解java高并发的用户线程和守护线程”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解java高并发的用户线程和守护线程”吧!守护线程是一种特殊的线程,在后台默
2023-06-25

Python怎么使用MapReduce编程模型统计销量

这篇文章主要介绍了Python怎么使用MapReduce编程模型统计销量的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Python怎么使用MapReduce编程模型统计销量文章都会有所收获,下面我们一起来看看吧
2023-06-30

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录