消息循环是不是在 UI 线程上执行?

Posted

技术标签:

【中文标题】消息循环是不是在 UI 线程上执行?【英文标题】:Does message loop execute on UI thread?消息循环是否在 UI 线程上执行? 【发布时间】:2016-11-18 09:18:21 【问题描述】:

我很困惑 winforms 中的消息循环是如何工作的。我正在处理 Windows 表单,我知道何时调用 Application.Run(myform),因此它将创建一个消息队列和消息循环,然后显示我的表单并开始在消息队列中检索消息以进行调度。

我阅读了一些主题,但仍然不明白消息循环在幕后是如何工作的,UI Thread both running message pump AND executing code? 和 How does the message loop use threads?

我的问题是:消息循环是否会在 UI 线程上运行。如果是,为什么它不阻塞 UI 线程?

如果问题不清楚,请告诉我,对不起,因为我的英语不好。

【问题讨论】:

这是在 Delphi 的上下文中编写的,但是您可以将 ProcessMessages 替换为 DoEvents 其余部分相同:***.com/a/25182276/327083 UI Thread both running message pump AND executing code?的可能重复 请注意 - 消息队列已经存在。 Application.Run 的主要作用是启动消息循环。这实际上只是一个从队列中获取消息并处理它们的几乎无穷无尽的循环。消息循环不是完全 UI 线程,但在 99% 的情况下,您可以将它们视为相同的东西。所有这些 UI 操作(例如单击按钮)都作为消息发送到队列,在循环中执行并在您的代码中进行处理。这是因为 Windows 窗口系统是基于消息/事件的 - UI 线程不会主动轮询输入状态,它会等待消息。 "消息循环是否会在 UI 线程上运行。如果是,为什么它不阻塞 UI 线程?"消息循环是 UI 线程,所以这个问题在这种情况下没有任何意义。 @Richard Everett,对不起,我在发布此主题之前阅读了该主题,它真的很短,我不明白这一点。实际上我需要更多解释! 【参考方案1】:

这个示例代码可以大致解释消息循环:

Message message;
while (PeekMessage(out message))
    ProcessMessage(messae);

是的,这“阻塞”了线程。线程将始终等待消息出现(PeekMessage 应在消息准备好时返回)或处理消息。

重要的是要知道 this 是不会阻塞 UI 的魔法。 UI 需要响应的任何内容都作为消息处理。

非常重要的部分是,为了避免阻塞 UI,无论 ProcessMessage 为处理单个消息所做的工作不应花费太长时间

如果用户点击一个按钮,这将作为消息处理。作为处理该消息的一部分,将调用 your 事件处理程序。如果该事件处理程序开始做一些冗长的事情,您不会返回到消息循环,因此它会在您的按钮单击事件处理程序运行时停止处理消息。

是的,消息循环在 UI 线程上运行。是的,它也阻塞了 UI 线程,但它继续运行的事实意味着它没有阻塞 UI


受到评论的启发,我认为我应该发布一个类比来解释循环工作的原因。

想想供应食物的当地熟食店。有些食物是现成的,只需要卖给消费者,有些食物必须按需准备。

这家熟食店有几个典型的操作:

    必须接受食物订单 食物必须准备好或取来 食物必须提供给消费者

现在,考虑一个桌子后面只有 1 个人的熟食店。

此人为每个消费者执行这三个操作。显然,如果一位消费者订购了必须当场准备好的食物,其余消费者将不得不等待。

另一方面,如果这些订单可以交给厨师,即接单员工的同事,那么接单的速度会更快。

在 Windows 中,消息循环是这个接受订单的员工,而且只有他。

作为处理订单(消息)的一部分,此人必须先检查其他人,然后等待,然后再将食物(结果)送回消费者手中。因此,任何冗长的处理都会减慢该队列的处理速度。

另一方面,如果“其他人”只是说,你回到队列并告诉消费者他会在食物准备好后得到食物,那么队列就会移动,即使现在有人在等他们的食物

这是为了保持 UI 响应而引入的典型线程或异步处理。

【讨论】:

感谢您的帮助,但您能否详细说明“它继续运行的事实意味着它没有阻塞 UI。”。所以这意味着我们感觉 UI 没有冻结的原因是因为消息循环无限运行?如果我们在事件处理程序中执行类似 Thread.Sleep(1000) 的操作与函数 ProcessMessage(message) 相同,那么执行代码需要很长时间吗?谢谢。 是的,完全正确。如果您在处理需要大量时间的消息时执行任何操作,则该代码必须在返回消息循环之前完成。如果消息在那个时间从操作系统和窗口系统进来,它们将被简单地排队而不被处理。 一个简单的类比是想象某人站在当地熟食店的点餐台前。如果他所做的只是接受命令,那么人们给他命令的队列将会很快过去。是的,他们在某些时候必须等待食物,但那个队列正在快速移动。如果他还要制作食物,那么排队的时间会慢一些。在 Windows 中,此队列的速度与用户界面对用户的响应速度直接相关。让 UI 感觉响应灵敏但做昂贵事情的典型技巧是聘请厨师(线程)并快速移动队列。 非常感谢您花时间为我解释,非常简洁明了。【参考方案2】:

消息循环是否在 UI 线程上运行?

是的。确实如此。

如果是,为什么它不阻塞 UI 线程?

您应该将消息循环视为无限循环:它一直在运行。每当队列中有要处理的消息时,它就会将其拾取并执行。这可以是一个被点击的按钮。那时,按钮会收到有关事件的通知,它将运行与其关联的代码,例如您的 button_Click 事件处理程序。如果您将Thread.Sleep(10000) 放入其中,您会注意到它将在睡眠期间(10 秒)阻塞整个应用程序。您无法调整大小、重绘或执行任何其他操作,因为消息循环也被阻止。一旦事件处理程序结束,它将继续从队列中获取下一条消息。

【讨论】:

非常感谢,因为帮助我更多地了解 winforms 在幕后的工作原理。

以上是关于消息循环是不是在 UI 线程上执行?的主要内容,如果未能解决你的问题,请参考以下文章

android Service中多线程交互

Handler Looper Message异步消息处理线程机制( hander消息机制原理)

消息机制

Android 线程与消息 机制 15问15答

Android消息循环机制

关于 SWT 的UI线程和非UI线程