监视消息循环

发布时间:2026/7/12 6:26:34

监视消息循环
我们讨论了消息循环是响应用户输入的根本还提到了在WinForm中执行耗时操作是因为这个耗时操作与消息循环在同一个UI Thread上导致不能处理用户的后续响应造成程序假死。除此之外还说到了Form中的WndProc方法说这个方法就是Win32时代那个处理消息的方法的.Net版。那么今天这篇文章我们就来编个小程序来模拟一下这个耗时操作看看是不是如上一篇所说耗时操作造成消息循环的临时中断不能响应用户后续输入。程序很简单就是一个简单的窗体上面放置一个按钮按钮里有一个Thread.Sleep(50*1000)模拟耗时操作public partial class LongTimeForm : Form{public LongTimeForm(){InitializeComponent();Debug.Listeners.Add(new ConsoleTraceListener());}private void btnLongTime_Click(object sender, EventArgs e){Thread.Sleep(50 * 1000);}//既然这个WndProc是Win32中处理消息的方法的.Net版那么我们应该在这里可以监视到所有用户操作的“消息”protected override void WndProc(ref Message m){Debug.WriteLine(m.Msg.ToString());base.WndProc(ref m);}}WndProc是一个虚方法我们可以override那么我们就来看看当用户点击“耗时操作”的按钮后这个方法是不是还能接收到用户其他的输入呢。小技巧在开发WinForm程序时为了方便显示程序的一些操作日志我们经常将项目属性里的“Windows Application”项目类型修改为“Console Application”这样在启动程序后除了会显示窗体外还会显示一个控制台在控制台里会显示程序里通过Debug.Write等输出的日志。当产品发布的时候我们可以将项目属性修改回”Windows Application”。启动程序鼠标在窗体上滑动后面的控制台上就会显示很多的数字这些都是消息循环从消息队列里取出的消息数字是消息的类别在Windows的一个头文件里定义然后传递给WndProc方法处理的控制台上还能输出数字说明现在消息循环还是“流畅的”。当我们点击“耗时操作”按钮后我们发现窗体这个时候“死掉了”不能再接受用户任何的操作而不管鼠标如何在窗体上滑动、点击后面的控制台没有一条输出。唔消息循环阻塞了。Thread.Sleep是50s时间50s后程序又活过来了控制台上又有源源不断的输出了。虽然我们用实例证明了上一篇所说的东西貌似是正确的但是难道我们就对这种耗时操作无能为力了么不啊我们有多线程啊我们有异步操作啊。我们就来看看如何使用异步操作来处理这种耗时操作。使用委托中的BeginInvoke进行异步操作还记得委托的BeginInvoke方法和EndInvoke方法么今儿我们就用这两个方法来做一个异步的操作(.Net中如果你看到这种BeginXXX和EndXXX成对出现的方法那说明就是可以进行异步操作了)。当你定义一个委托后编译器会自动的为你生成一个类还会为你在这个类里提供一个BeginInvoke方法和一个EndInvoke方法这两个方法的实现是由CLR提供的而这个BeginInvoke和EndInvoke只是起一个包装的作用。我们还是先来看看将上面的耗时操作修改为异步的代码吧public partial class LongTimeForm : Form{public LongTimeForm(){InitializeComponent();Debug.Listeners.Add(new ConsoleTraceListener());}private void LongTimeMethod(){Thread.Sleep(50 * 1000);}private void btnLongTime_Click(object sender, EventArgs e){//咱们这儿就不自己定义新的委托了.Net为我们定义了一串的通用委托使用Action longTimeAction new Action(LongTimeMethod);longTimeAction.BeginInvoke(null, null);}protected override void WndProc(ref Message m){Debug.WriteLine(m.Msg.ToString());base.WndProc(ref m);}}现在再来运行程序。哇塞虽然“耗时操作”的按钮点击后消息循环继续进行界面也没有假死。嗯这才是我们要的用户体验当然我们应该还给用户一些提示说耗时操作正在进行请不要关闭窗口。接下来我们来看看这个异步操作是咋实现的。首先我们在LongTimeMethod方法里设置断点点击“耗时操作”按钮后等待命中断点断点命中后选择Visual Studio的“Debug”-”Windows”-”Threads”这样会打开线程的窗口在这里我们可以看到类似下图的图片有箭头指示的是正在执行的LongTimeMethod方法的Worker Thread从这里可以看出Main方法是Main Thread中看来BeginInvoke就是从Thread Pool中选择一个空闲线程来执行我们的耗时操作。我提到这里是因为经常有人问我异步和多线程有什么区别其实异步是目的而多线程是实现这个目的的方法。异步是说A发起一个操作后一般都是比较耗时的操作如果不耗时的操作就没有必要异步了可以继续自顾自的处理它自己的事儿不用干等着这个耗时操作返回。.Net中的这种异步编程模型就简化了多线程编程我们甚至都不用去关心Thread类就可以做一个异步操作出来。去了还要回实际上上面演示的耗时操作是“一去不复返”的操作相当于WCF中的One-Way操作也就是我发起这个操作后我就不用管它了我甚至不关心它运算的结果。但大部分时候我们需要这样的操作执行完后返回来更新一下UI比如告诉用户一声我执行完了或者显示执行结果。那这样我们就要考虑异步调用的几种方式了。如果我们要从异步操作里获取结果我们就得调用EndInvoke方法那我们又用什么手段来得到异步操作完成的信号呢因为如果异步操作没有完成我们就直接调用EndInvoke方法这样就会阻塞一直等到异步操作执行完毕后才会执行。在继续讨论之前我们来看看BeginInvoke的返回值1: public interface IAsyncResult2: {3: object AsyncState { get; }4:5: WaitHandle AsyncWaitHandle { get; }6:7: bool CompletedSynchronously { get; }8:9: bool IsCompleted { get; }10: }根据BeginInvoke返回的结果我们就有两种调用异步操作的方式轮询IAsyncResult的IsCompleted属性会在异步操作结束后返回true否则返回false。那么我们就可以用一个循环不断的访问IsCompleted属性当IsCompleted为true的时候再调用EndInvoke方法1: public partial class LongTimeForm : Form2: {3: public LongTimeForm()4: {5: InitializeComponent();6: Debug.Listeners.Add(new ConsoleTraceListener());7: }8:9: private int LongTimeMethod()10: {11: Thread.Sleep(50 * 1000);12: return 10;13: }14:15: private void btnLongTime_Click(object sender, EventArgs e)16: {17: //咱们这儿就不自己定义新的委托了.Net为我们定义了一串的通用委托使用18: Funcint longTimeAction new Funcint(LongTimeMethod);19: IAsyncResult asynResult longTimeAction.BeginInvoke(null, null);20:21: //可以做别的事情22: while (!asynResult.IsCompleted)23: {24:25: }26: int result longTimeAction.EndInvoke(asynResult);27:28: }29: protected override void WndProc(ref Message m)30: {31: Debug.WriteLine(m.Msg.ToString());32: base.WndProc(ref m);33: }34: }WaitOne在IAsyncResult里还有一个AsyncWaitHandle属性这是一个WaitHandle类型的属性这个对象有一个WaitOne方法还能接受一个超时时间它会等待这个超时时间指定的长度1: private int LongTimeMethod()2: {3: Thread.Sleep(50 * 1000);4: return 10;5: }6: private void btnLongTime_Click(object sender, EventArgs e)7: {8: Funcint longTimeAction new Funcint(LongTimeMethod);9: IAsyncResult asynResult longTimeAction.BeginInvoke(null, null);10:11: //可以继续处理别的事情12:13: if (asynResult.AsyncWaitHandle.WaitOne(10000, true))14: {15: int result longTimeAction.EndInvoke(asynResult);16: }17: }上面的代码的意思就是异步调用耗时操作后继续干自己的事儿然后干完自己的事儿再来等着一个信号啥信号呢就是这个耗时操作完成的信号。而且您还别让我等得太久等久了我就不耐烦了我可只等待10秒钟啊。晕死上面这耗时操作就要执行50秒钟你就等10秒钟这不是玩我吗10秒钟时间过去了这个WaitOne就不再等待了线程将继续执行。回调其实不管是上面使用轮询的方式还是使用WaitOne等待一个信号量还是要等待。等待是个让人很恼火的事情。.Net考虑了这一点为我们准备了回调的方式你异步调用后继续干你的事儿等你执行完后你告我一声就ok了。1: private void btnLongTime_Click(object sender, EventArgs e)2: {3: Funcint longTimeAction new Funcint(LongTimeMethod);4: //这里使用了一个lambda表达式省了不少力啊5: IAsyncResult asynResult longTimeAction.BeginInvoke((result) {6: int ret longTimeAction.EndInvoke(result);7: }, null);8: }当异步操作完成后上面代码中用lambda表达式表示的一个回调方法就会执行在这里调用EndInvoke获取耗时操作的结果。在这里想想为什么用lambda如果不用lambda也不用匿名方法不管你用啥实际上就是形成一个闭包你要怎么做留作您自己思考。更新UI上面四种异步调用的方式一种无声无息一去不复返。一种轮询、一种等待外加一个回调。实际上耗时操作的结果都让代码给“吃”了。一般情况下我们处理完耗时操作总要有所表现吧比如更新一下UI等等。那我们就来看看如何更新UI。当你运行这个程序时当耗时操作结束后啪嚓一下程序出异常了啊为什么啊为什么就不行啊。还不能从不是创建这个控件的线程中访问这个控件。那怎么

相关新闻

3分钟学会Windows USB设备共享:usbipd-win让你的开发效率翻倍!

3分钟学会Windows USB设备共享:usbipd-win让你的开发效率翻倍!

2026/7/10 22:19:43

3分钟学会Windows USB设备共享:usbipd-win让你的开发效率翻倍! 【免费下载链接】usbipd-win Windows software for sharing locally connected USB devices to other machines, including Hyper-V guests and WSL 2. 项目地址: https://gitcode.com/gh…

AI 代码验证沙箱:题解能运行,也不能随便运行

AI 代码验证沙箱:题解能运行,也不能随便运行

2026/7/11 5:27:12

AI 代码验证沙箱:题解能运行,也不能随便运行 一、自动验证需要安全边界 AI 生成题解后,最好自动运行测试,验证代码是否通过样例和随机用例。但“运行代码”本身有风险。代码可能死循环、占满内存、读写文件、访问网络,…

程序员的光荣与梦想——论侠客梦的延续与幻灭

程序员的光荣与梦想——论侠客梦的延续与幻灭

2026/7/7 5:51:19

这不是很奇怪么?所有行业的初学者都可以被称作“菜鸟”,但是只有电脑高手(特别是程序牛人)被称作“大侠”。这到底是巧合呢,还是另有原因?今天1-2-3吃饱了撑着没事干,跟大家一起YY下这个问题。 …

YOLO + LSTM 实现连续动作识别、工序计数和工序计时

YOLO + LSTM 实现连续动作识别、工序计数和工序计时

2026/7/12 6:24:02

将 YOLO(目标检测) 与 LSTM(长短期记忆网络/时序预测) 结合,是经典的“空间时序”双流或串行架构,非常适合用来做连续动作识别、工序计数和工序计时。 本文为你拆解它的实现原理以及在工厂落地时的具体方案…

如何快速掌握缠论分析:通达信ChanlunX插件完整指南

如何快速掌握缠论分析:通达信ChanlunX插件完整指南

2026/7/12 6:24:02

如何快速掌握缠论分析:通达信ChanlunX插件完整指南 【免费下载链接】ChanlunX 缠中说禅炒股缠论可视化插件 项目地址: https://gitcode.com/gh_mirrors/ch/ChanlunX 缠论分析是股票技术分析中的重要方法,但复杂的理论体系让许多投资者望而却步。通…

Seedream 5.0 Pro:多模态AI如何重塑数据可视化工作流

Seedream 5.0 Pro:多模态AI如何重塑数据可视化工作流

2026/7/12 6:24:02

如果你还在为数据报告、产品演示或技术文档的图表制作而头疼,每天在PPT和设计软件之间反复切换,那么字节跳动最新发布的Seedream 5.0 Pro可能正是你需要的解决方案。这个多模态图像创作模型的核心突破在于:它能将复杂的结构化信息直接转化为高…

从日德兰海战看复杂系统设计:技术细节、信息流与决策如何决定成败

从日德兰海战看复杂系统设计:技术细节、信息流与决策如何决定成败

2026/7/12 6:24:02

1. 项目概述:一场被遗忘的钢铁对决提起第一次世界大战,大多数人脑海中浮现的是泥泞的堑壕、索姆河的绞肉机,或是天空中刚刚崭露头角的双翼机。然而,在北海的浓雾与波涛之下,一场规模空前、足以决定整个战争走向的钢铁巨…

2026靠谱程序员兼职平台全分类推荐 适配不同技术层级接单指南

2026靠谱程序员兼职平台全分类推荐 适配不同技术层级接单指南

2026/7/12 6:24:02

国内中小企业数字化转型提速、远程灵活用工模式普及,程序员技术兼职、远程接单已成为主流副业变现方式。当下自由开发者接单行业日趋成熟,但市场平台资质参差不齐,据行业调研数据显示,超73%兼职程序员曾遭遇尾款拖欠、需求无限变更…

Word 隐藏文字功能深度解析:3个高级应用场景与2个关键限制

Word 隐藏文字功能深度解析:3个高级应用场景与2个关键限制

2026/7/12 6:14:01

Word隐藏文字功能深度解析:3个高级应用场景与2个关键限制在文档协作与版本管理的复杂场景中,Word的隐藏文字功能常被低估。这个看似简单的功能,实则是中高级用户实现文档智能化的秘密武器。不同于基础操作指南,本文将带您探索隐藏…

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

2026/7/12 0:03:42

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator XUnity.AutoTranslator作为Unity游戏社区中最成熟的文本翻译解决方…

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

2026/7/12 0:03:42

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持 【免费下载链接】raspberrypi-kernel It provides openEuler kernel source for Raspberry Pi 项目地址: https://gitcode.com/openeuler/raspberrypi-kernel 前往项目官网免费下载&…

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

2026/7/12 0:03:42

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧 【免费下载链接】integration-test The repo contains test suits for system integration test 项目地址: https://gitcode.com/openeuler/integration-test 前往项目官网免费下载:…

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

2026/7/12 0:03:42

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator XUnity.AutoTranslator作为Unity游戏社区中最成熟的文本翻译解决方…

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

2026/7/12 0:03:42

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持 【免费下载链接】raspberrypi-kernel It provides openEuler kernel source for Raspberry Pi 项目地址: https://gitcode.com/openeuler/raspberrypi-kernel 前往项目官网免费下载&…

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

2026/7/12 0:03:42

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧 【免费下载链接】integration-test The repo contains test suits for system integration test 项目地址: https://gitcode.com/openeuler/integration-test 前往项目官网免费下载:…