WPF 消息循环(二)
们已经知道,win32/MFC/WinForm/WPF 都依靠消息循环驱动,让程序跑起来。
这里就介绍 WPF 中是如何使用消息循环来驱动程序的。

1. 背景
只听说过 Dispatcher ,哪里来的消息循环?
WPF 启动运行堆栈:
> WpfApp1.exe!WpfApp1.App.OnStartup(System.Windows.StartupEventArgs e) 行 17 C#PresentationFramework.dll!System.Windows.Application..ctor.AnonymousMethod__1_0(object unused) WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext, System.Threading.ContextCallback callback, object state) WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) [本机到托管的转换] [托管到本机的转换] WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) WpfApp1.exe!WpfApp1.App.Main()
可以发现 倒数第 4 行的PushFrameImpl 这个方法。
去看其源码,就发现了熟悉的消息循环 :
private void PushFrameImpl(DispatcherFrame frame)
{SynchronizationContext synchronizationContext = null;SynchronizationContext synchronizationContext2 = null;MSG msg = default(MSG);_frameDepth++;try{synchronizationContext = SynchronizationContext.Current;synchronizationContext2 = new DispatcherSynchronizationContext(this);SynchronizationContext.SetSynchronizationContext(synchronizationContext2);try{while (frame.Continue && GetMessage(ref msg, IntPtr.Zero, 0, 0)){TranslateAndDispatchMessage(ref msg);}if (_frameDepth == 1 && _hasShutdownStarted){ShutdownImpl();}}finally{SynchronizationContext.SetSynchronizationContext(synchronizationContext);}}finally{_frameDepth--;if (_frameDepth == 0){_exitAllFrames = false;}}
}
主要的 GetMessage 和 TranslateAndDispatchMessage 代码:
private bool GetMessage(ref MSG msg, IntPtr hwnd, int minMessage, int maxMessage)
{UnsafeNativeMethods.ITfMessagePump messagePump = GetMessagePump();try{if (messagePump == null){return UnsafeNativeMethods.GetMessageW(ref msg, new HandleRef(this, hwnd), minMessage, maxMessage);}messagePump.GetMessageW(ref msg, hwnd, minMessage, maxMessage, out var result);return result switch{-1 => throw new Win32Exception(), 0 => false, _ => true, };}finally{if (messagePump != null){Marshal.ReleaseComObject(messagePump);}}
}
private void TranslateAndDispatchMessage(ref MSG msg)
{bool flag = false;if (!ComponentDispatcher.RaiseThreadMessage(ref msg)){UnsafeNativeMethods.TranslateMessage(ref msg);UnsafeNativeMethods.DispatchMessage(ref msg);}
}
可以理解为:Dispatcher 对消息循环的操作进行了“封装” 。
那,Dispatcher 是谁?
2. Dispatchcer
大部分WPF对象,都是 DispatcherObject。这意味着,可以在 DispatcherObject 中(如 Window 中), 使用 this.Dispatchcer 获取到 Dispatchcer 。

一般我们会通过三种方式获取 Dispatchcer :
// App.Current.Dispatcher;(Application.Current.Dispatcher)
var dispatcher1 = App.Current.Dispatcher;// CurrentDispatcher;
var dispatcher2 = System.Windows.Threading.Dispatcher.CurrentDispatcher;// System.Windows.Threading.DispatcherObject.Dispatcher;
var dispatcher3 = this.Dispatcher;
可分为两类:
-
当前线程的 Dispatcher:
System.Windows.Threading.Dispatcher.CurrentDispatcher;
-
创建对应对象的 Dispatcher:
App.Current.Dispatcher;
DispatcherObject.Dispatcher;
Dispatcher 和线程是什么关系?
- Dispatcher 属于线程(与线程一一对应)。
在单 UI 线程应用程序中,WPF的 UI 对象在获取 this.Dispatcher 属性时,不同对象取的都是同一个Dispatcher实例。(因为都是同一个 UI 线程创建的。)- 在默认的 WPF UI 线程中: App.Current.Dispatcher = DispatcherObject.Dispatcher
所有的线程(UI线程,普通线程)都有 Dispatcher 吗?
是的。
在所有线程中,调用 System.Windows.Threading.Dispatcher.CurrentDispatcher
都会得到一个属于这个线程的 Dispatcher 对象。(不用的时候不会创建)
所以:如果你想在一个后台线程中,使用 Dispatcher.CurrentDispatcher.Invoke
将操作封送到 UI 线程,是做不到的。因为这时候获取到的 Dispatcher 不是UI线程的 Dispatcher, 而是当前线程自己的 Dispatcher。
你最常用 Dispatcher 做什么?
跨线程访问UI
为什么不能直接跨线程访问UI?

3. Dispatcher 如何实现跨线程的调用
最常使用 Dispatcher 的创建就是,在后台线程更新 UI ,那 Dispatcher 是如何做到的呢。
当你调用
private void Foo()
{this.Width = 100;
}Application.Current.Dispatcher.Invoke(() =>
{Foo();
});
时,Dispatcher 究竟做了什么,把操作转移到 UI 线程上去了。

- 将调用的
Delegate和优先级包装成一个DispatcherOperation放入Dispatcher维护的优先级队列当中,这个Queue是按DispatcherPriority排序的,总是高优先级的 DispatcherOperation 先被处理。 - 往当前线程的消息队列当中Post一个名为
MsgProcessQueue的 Message。(这个消息是WPF自己定义的。)这个消息被Post到消息队列之前,还要设置MSG.Handle,这个Handle就是Window 1#的Handle。指定Handle是为了在消息循环Dispatch消息的时候,指定哪个窗口的 WndProc 处理这个消息。 - 消息循环读取消息。
- 系统根据获取消息的Handle,发现跟Window1#的Handle相同,那么这个消息派发到Window1#的窗口过程,让其处理。
- 在窗口过程中,优先级队列当中取一个DispatcherOperation。
- 执行 DispatcherOperation.Invoke 方法,Invoke方法的核心就是调用 DispatcherOperation 构造时传入的Delegate,也就是 Dispatcher.BeginInvoke 传入的Delegate。最终这个Foo()方法就被执行了。
4 回顾
- WPF 底层仍然靠信息循环来驱动。
- Dispatcher 使用消息循环来实现跨进程的委托调用。
- Dispatcher 属于线程,需要理解当前拿到的 Dispatcher 到底是哪个 Dispatcher 。
相关文章:
WPF 消息循环(二)
们已经知道,win32/MFC/WinForm/WPF 都依靠消息循环驱动,让程序跑起来。 这里就介绍 WPF 中是如何使用消息循环来驱动程序的。 1. 背景 只听说过 Dispatcher ,哪里来的消息循环? WPF 启动运行堆栈: > WpfApp1.…...
ubuntu上更改ext4格式的硬盘为 windows的 NTFS 格式参考
1. ubuntu上安装 sudo apt-get install gparted 2. 参考如下,下面是转换后的样例。 3.windows上添加识别新硬盘参考 先在设备管理器中 找到下面 磁盘管理 如下:找到类似下面的磁盘2 查看相关信息 右键可以新建卷和格式化,下面是已经新建…...
Fastapi教程:使用 aioredis 连接池执行Redis 的高效异步操作
在构建高性能的 Web 应用时,缓存系统是一个至关重要的组成部分。Redis 是最常见的缓存系统之一,它提供了高效的存储与读取机制。然而,在与 Redis 进行频繁交互时,创建和销毁连接可能会成为瓶颈。为了优化这一问题,我们…...
配置mysqld(读取选项内容,基本配置),数据目录(配置的必要性,目录下的内容,具体文件介绍,修改配置)
目录 配置mysqld 读取选项内容 介绍 启动脚本 基本配置 内容 端口号 数据目录的路径 配置的必要性 配置路径 mysql数据目录 具体文件 修改配置时 权限问题 配置mysqld 读取选项内容 介绍 会从[mysqld] / [server] 节点中读取选项内容 优先读取[server] 虽然服务…...
docker 容器相互访问
目前采用 network 方式 1. 创建自定义网络 docker network create network-group 如下 2. 相互访问的容器更改(目前演示redis 以及netcore api 访问redis ) //redis 原有容器删除 跟之前区别就是加入 --network network-group docker run \ -p 6379:…...
算法1(蓝桥杯18)-删除链表的倒数第 N 个节点
问题: 给你一个链表,删除链表的倒数第 n 个节点,并且返回链表的头节点。 输入:head 1 -> 2 -> 3 -> 4 -> 5 -> null, n 2 输出:1 -> 2 -> 3 -> 5 -> null输入:head 1 ->…...
【PyTorch】动态调整学习率 torch.optim.lr_scheduler.StepLR 调度器
文章目录 1. torch.optim.lr_scheduler.StepLR 官方文档详解2. 使用示例2.1 官方提供使用示例2.2 自己写代码测试方法2.2.1 get_last_lr() 方法2.2.2 state_dict() 方法2.2.3 load_state_dict() 保存和加载调度器 3. 思考3.1 为什么需要state_dict()3.2 get_lr() 与 get_last_l…...
AIGC drug design 人工智能生成式药物设计:基于 GPT 的 SMILES 生成与应用
人工智能生成式药物设计:基于 GPT 的 SMILES 生成与应用 1. 人工智能生成模型:解密 GPT 的工作原理 目录 引言 1.1 背景介绍 1.2 人工智能生成模型的兴起 1.3 GPT 系列模型的地位与影响 GPT 模型概述 2.1 什么是 GPT 2.2 GPT 的发展历程 2.3 GPT 与其…...
Python面试常见问题及答案4
一、内存管理相关 问题:Python中的垃圾回收机制是如何工作的? 答案:Python主要使用引用计数来进行垃圾回收,当对象的引用计数为0时,该对象就会被垃圾回收器回收。此外,Python还有一个循环垃圾收集器来处理循…...
开启第二阶段---蓝桥杯
一、12.10--数据类型的范围及转化 今天是刚开始,一天一道题 对于这道题我想要记录的是Java中的整数默认是 int 类型,如果数值超出了 int 的范围,就会发生溢出错误。为了避免这个问题,可以将数字表示为 long 类型,方法…...
npm内存溢出
项目过大运行项目内存溢出 报错代码 运行内存溢出 increase-memory-limit ‘“node --max-old-space-size8192”’ 不是内部或外部命令,也不是可运行的程序 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of m…...
回归预测 | MATLAB实现CNN-BiGRU卷积神经网络结合双向门控循环单元多输入单输出回归预测
回归预测 | MATLAB实现CNN-BiGRU卷积神经网络结合双向门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现CNN-BiGRU卷积神经网络结合双向门控循环单元多输入单输出回归预测预测效果基本介绍程序设计参考资料预测效果 基本介绍 CNN-BiGRU,即卷积神经网络(CNN)与双…...
Android系统卡启动问题排查
Android系统启动正常来说会涉及到如下几个过程: 引导加载程序(Bootloader)Linux内核(Kernel),负责硬件抽象、内存管理、进程管理、网络堆栈等init进程 init进程读取init.rc配置文件,用于启动各…...
STP(生成树协议)
STP的基本概念 概述 STP是一个用于局域网中消除环路的协议。运行该协议的设备通过彼此交互信息而发现网络中的环路,并对某些接口进行阻塞以消除环路。STP在网络中运行后会持续监控网络的状态,当网络出现拓扑变更时,STP能够感知并且进行自动…...
【前端面试】随机、结构赋值、博弈题
解构赋值(Destructuring Assignment)是 JavaScript ES6 引入的一项非常有用的特性,它允许我们快速地从数组或对象中提取值,并将它们赋给变量。这种方式使得代码更加简洁、易读,并且能够减少重复的访问和赋值操作。 1.…...
Volta——开箱即用的Node.js 版本管理工具
Volta volta 是一个较新的 Node.js 版本管理器,旨在简化 Node.js 和其他工具的安装和管理,在 2019 年出世,仍在积极开发中。Volta 采用了与 nvm 不同的方法:它不是管理 Node.js 的多个版本,而是管理项目及其依赖项。当…...
ubuntu 磁盘空间满,找不到占用文件的目录
解决方法: 检查磁盘空间: 执行 df -h 查看各分区磁盘使用情况。 查找大文件或目录: 执行 du -sh /* 2>/dev/null 查找根目录下的大文件或目录,再逐一进入子目录使用相同命令查找。 清理缓存和临时文件: 清理 /t…...
1. 机器学习基本知识(5)——练习题(参考答案)
20.🔗本章代码笔记📓链接(需要🪜):(01_the_machine_learning_landscape.ipynb - Colab (google.com)) 如果你不想通过上面的官方网址下载本章的笔记,还可以在本篇博文的…...
spark-sql 备忘录
wordcount sc.textFile("../data/data.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(__).collect 读取json 文件 并通过sql 执行 join 查询 public static void main(String[] args) {SparkSession session SparkSession.builder().master(&qu…...
基于softmax回归的多分类
基于softmax回归的多分类任务是机器学习领域中的一种常见应用。softmax回归,又称多项逻辑回归或多类逻辑回归,是逻辑回归在多分类问题上的推广。以下是对基于softmax回归的多分类任务的详细解释: 一、softmax回归的原理 softmax回归的核心思想是通过softmax函数将输入数据…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
