我不明白Delphi中的Application.ProcessMessages正在做什么

时间:2014-08-07 11:47:18

标签: delphi

我是德尔福的noobie,对于任何愚蠢的问题都很抱歉。

我的主管向我解释说Application.ProcessMessages可以防止冻结应用程序并分配一些额外的计算时间。但是在这个命令的文档中总是会解释一个被处理的队列系统? 请问有人可以解释一下背景吗?

2 个答案:

答案 0 :(得分:45)

没有简短的方法可以正确回答这个问题。

Windows应用程序与操作系统交互的主要方式是通过消息传递系统。在Windows应用程序中发生的一切都是在响应消息时发生的。

例如:

如果您点击屏幕,操作系统会决定点击了哪个应用程序,并向该应用程序发送消息,表明已收到点击(以及该点击的位置)。

如果窗口被移动并在其下方显示应用程序的一部分,操作系统会发送一条消息,告诉您的应用程序重绘自己。

列表还在继续。发生的一切都是由消息驱动的。

现在,每个应用程序都有一个主用户界面线程(" Main"线程),并且该线程有一个主要功能 - 它在无限循环中运行,从操作系统检查这些消息然后执行必要的代码以响应这些消息。

问题

你过来开始编写一个应用程序。你可以写一些像这样的代码:

procedure TForm1.Button1Click(Sender: TObject);
var i : Integer;
begin
  for i := 0 to 99999999999 do begin
    SolveTheProblemsOfTheWorld(i);
    CalculatePiToABillionPlaces;
  end;
end;

在程序的较大结构中,主线程中的执行如下所示:

  1. 检查消息
  2. 对于每条消息 - 执行关联的处理程序
  3. 返回检查消息(循环)
  4. 因此,当突然有一个关联的处理程序(上面的Button1Click)开始花费很长时间才能完成时,这个循环很愉快。 理解的关键是一个消息处理程序必须在下一个消息处理程序运行之前完成。例如,如果单击滚动条并拖动它,但是您已将处理程序附加到{{1滚动条需要10秒才能完成,然后在单击处理程序完成之前,应用程序将不会看到拖动操作。与此同时,消息队列正在填满,主线程没有对此做任何事情。

    当然,您已经体验过这一点 - 突然您的应用程序无法响应点击次数。你不能与它互动,你不能移动窗口,如果你拖动另一个窗口,应用程序甚至不会重绘自己 - 它只是填满你留下的任何垃圾顶部。

    输入ProcessMessages

    不将长时间运行的代码放入线程的懒惰,可怕的解决方案

    当你正在调用OnClick时,你正在做的是在这些处理程序之一的中间,指示主线程休息一下以返回检查消息队列并清空所有消息堆积;处理任何点击,窗口移动,输入,击键,在需要时重新绘制自己等等。

    Application.ProcessMessages

    这似乎表面上看起来很明智,因为它允许您在长时间运行的循环中保持应用程序的响应。然而,最终,由于许多非常好的理由,这种编程风格被广泛认为是极差的实践。我只想说,在你试图使用procedure TForm1.Button1Click(Sender: TObject); var i : Integer; begin for i := 0 to 99999999999 do begin SolveTheProblemsOfTheWorld(i); CalculatePiToABillionPlaces; Application.ProcessMessages; end; end; 的任何地方,都可以将这项工作转移到后台线程。

    有关详细信息,请查看实际代码:

    Application.ProcessMessages

    因此,当您对procedure TApplication.ProcessMessages; var Msg: TMsg; begin while ProcessMessage(Msg) do {loop}; end; 进行此调用时,您正在逐个运行循环,清空消息队列中的消息(并执行附加到对这些消息作出反应的处理程序的所有代码)消息)直到它为空。当它为空并且没有其他消息要处理时,控制将返回到程序中的下一行。

    重要的一点是,使用程序时遇到的流畅,流畅的交互是一种完全错觉,它完全取决于正在尽快处理的消息循环。发布到您的应用程序的消息与正在处理的消息之间的时间延迟越小,您的应用程序就越感觉它是活着的和响应的。

    正是由于这个原因,附加到用户界面处理程序的所有代码都应该快速运行。需要中断长时间运行的操作,以便消息处理可以继续(即:Application.ProcessMessages)或者需要将这些操作移动到一个单独的线程,在那里它们可以执行而不会占用主线程并将其从主要责任(保持用户界面活着)。


    有关该主题的非常好的文章,请参阅:

    Peter下面的

    A Key's Odyssey

    Google cache link ...服务器似乎对我很忙)

    摘要:本文遵循通过VCL的击键消息的路径。您将学习如何实现密钥处理,OnKey事件如何工作以及在整个过程中可以找到程序员的干预点。此外,还解释了消息处理之类的内容,您将学习如何在调试器中将消息从消息循环跟踪到最终目标。

答案 1 :(得分:31)

<强>信息

Delphi VCL应用程序是一个Windows应用程序,Windows组件之间的大量通信(如表单,编辑框,以及计时器等隐藏的东西)都是使用名为 messages 的东西完成的。

这些消息将发送到特殊队列。您可以直接发送此类消息(使用SendMessagePostMessage API函数),但在设置属性时也会间接发送消息,例如Text的{​​{1}}

这些消息用于通知控件在程序中发生了重要的事件,以便他们可以做出适当的响应。发送和响应消息的概念是一种称为事件驱动编程的范式的核心原则。

许多现代操作系统都是事件驱动的,例如Windows。事实上,Delphi VCL包含了很多Windows功能,你做的很多事情都会导致在控件之间发送消息,这些消息会通知那些控制鼠标点击,键盘按下,Windows设置更改,应用程序关闭,控制移动等。

Mainthread在执行代码时不会处理消息

操作系统还允许程序拥有称为线程的东西。线程就像程序的一小部分似乎同时运行(在某些情况下实际上同时运行)。一个应用程序可以有多个线程,但总有主线程,它也是负责接受传入消息和更新GUI的线程。

当应用程序的主线程空闲(不执行代码)时,它将检查队列以查看是否有消息并处理它们。当其他不相关的代码正在执行时,此无法发生。考虑这个例子:

TEdit

如果您执行此代码,您会希望该标签获得“A&#39;”的标题,该标题会更改为&#39; B&#39;五秒钟后。但是这是错误的。设置标签的标题会触发通过消息重新绘制控件。由于主线程仍然被此代码阻止(即使是Sleep命令),因此该消息尚未处理。

只有当5秒钟过去且标题设置为&#39; B&#39;时,主线程才会变为空闲并执行我们通过设置标题触发的重绘。另请注意,其他交互(如单击按钮或拖动窗口)将推迟到5秒钟过后。当主线程执行该代码时,整个UI冻结。

Application.ProcessMessages to rescue

Application.ProcessMessages将强制应用程序清空其消息队列,因此您可以修复&#39;像这样的代码:

Label1.Caption := 'A';
Sleep(5000); // Sleep is simulating a long, blocking process.
Label1.Caption := 'B';

通常在代码中不仅仅是睡眠,而是大量的实际处理。通过频繁使用Label1.Caption := 'A'; Application.ProcessMessages; Sleep(5000); Label1.Caption := 'B'; ,即使执行代码,也可以使应用程序的界面保持响应。

但是这有点脏,主线程(和接口)仍将被阻止,除非你设法挤入很多Application.ProcessMessage来电。除了处理绘制消息之外,还会处理其他消息,因此应用程序可以在您的过程中处理点击事件。

因此,您阅读的文档是正确的:Application.ProcessMessages将清空消息队列。你的主管不完全正确。它没有为每个说法分配额外的处理时间,但它只是将消息队列的清空集成到执行的代码中,而通常消息将保留在队列中,直到应用程序变为空闲。

<强>塔内

在内部,应用程序本身始终如一。它的主要过程Application.ProcessMessages看起来像这样(简化的伪代码):

Application.Run

执行代码的处理,例如处理 repeat ProcessMessageFromTheQueue; If QueueIsEmpty then TellWindowsToWakeMeUpWhenANewMessageArrives; until Terminated; 消息时,它将触发(通过一些Delphi VCL魔法),WM_MOUSECLICK事件处理程序。由于这是一个单独的线程,所以一切都按顺序运行,因此您将了解Button1Click仅在事件处理程序完成时返回。 Delphi一次只处理一条消息,只处理完上一条消息时才执行下一条消息。

或者是吗?

ProcessMessageFromTheQueue看起来像这样:

Application.ProcessMessages

因此它与应用程序主循环几乎相同:它从队列中选取一条消息并对其进行处理。这意味着,您可以在处理最后一条消息时实际处理下一条消息。一个方便的技巧,但它可以很容易地使你的应用程序混乱,并导致各种不必要的副作用。

正确方法:主题

大多数人认为使用单独的线程执行代码并在需要更新标签或进度条时向主线程发送信号是更好的解决方案。这样,主线程一直处于空闲状态,并将继续响应鼠标点击等。

但是,对于初学者来说,线程很难。要记住的一些事情:

  • 线程(主线程除外)不应直接更新GUI(例如,线程无法设置Label1.Caption。可能失败。
  • 变量和其他资源不能同时由多个线程更改,因此您必须采取预防措施以防止(关键部分)。
  • 您必须(在大多数情况下)阻止用户执行已执行的相同操作。
  • 在许多情况下,如果用户想要取消流程或关闭应用程序,您必须找到正确的方法来中断正在运行的线程。

无论如何,这个答案不应该是关于线程的完整课程,但现在至少你知道 repeat ProcessMessageFromTheQueue; until QueueIsEmpty; 的用途。 还要注意,它被认为是“邪恶的”。许多人,但作为一个初学者,你可以尝试它自己找出它的优点和缺点。如果你这样做,我希望这些信息至少会激励你在下一章有足够经验的时候尽快转到线程。