Postmessage和sendmessage的替代方案

iam*_*joy 13 delphi

我有一个程序,它使用几个线程来执行某些任务.每个线程都有一堆任务要执行.在执行其中一个之后,每个thred将向主屏幕调用一条帖子消息来更新日志.

现在我有六万个任务,每个线程一万个 - 六个线程 - 在执行每个任务线程后调用post消息.但由于这些帖子消息,我的应用程序变得非常繁忙,看起来像是被绞死了.

如果我删除帖子消息......每件事情都可以.但是我无法直接调用该过程,因为它使用ui控件并且ui控件不是线程安全的,并且直接从线程调用过程将导致其他错误.

那么有什么替代品可用于postmessage和发送消息.

谢谢,bASIL

Ian*_*oyd 23

问题是发布消息有两个消息队列.这样做的结果是,发布的消息之前的任何总是被处理Paint,InputTimer消息.

这意味着您将使用几十条消息充斥消息队列.这些消息将始终在绘制和用户消息之前得到处理 - 使您的应用程序看起来挂起.

解决这个问题的常用方法是使用计时器; 让你的代码启动一个非常短的持续时间(例如0毫秒)计时器.

澄清

定时器消息(WM_TIMER),如Paint消息(WM_PAINT)和输入消息(例如WM_MOUSEMOVE,WM_KEYDOWN)发布消息处理.具体的定时器消息在输入和绘制消息后处理.

这意味着您的应用程序将处理下WM_TIMER一条消息之前响应用户事件和绘制请求.您可以通过使用Timer(及其WM_TIMER消息)来利用此行为,而不是自己发布消息(这将始终优先于绘制和输入消息).

当计时器开火时,他们会发送WM_TIMER信息.任何输入和绘制消息后,将始终处理此消息; 使您的应用程序显示响应.

设置计时器的另一个好处是它会强制您"排队"在(线程安全)列表中处理的所有项目.您将几千个工作项组合成一个"计时器",从而使系统不必生成和处理数千个不必要的消息.

奖金Chatter

...消息按以下顺序处理:

  • 发送信息
  • 发布消息
  • 输入(硬件)消息和系统内部事件
  • 已发送消息(再次)
  • WM_PAINT消息
  • WM_TIMER消息

更新:你应该不会有一个计时器投票,那只是浪费和错误的.你的线程应该" 设置一个标志 "(即计时器).这允许你的主线程实际上空闲,只有在有事情要做时才会醒来.

更新二:

表示不保留消息生成顺序的伪代码:

//Code is public domain. No attribution required.
const
  WM_ProcessNextItem = WM_APP+3;

procedure WindowProc(var Message: TMessage)
begin
   case Message.Msg of
   WM_Paint: PaintControl(g_dc);
   WM_ProcessNextItem:
      begin
          ProcessNextItem();
          Self.Invalidate; //Invalidate ourselves to trigger a wm_paint

          //Post a message to ourselves so that we process the next 
          //item after any paints and mouse/keyboard/close/quit messages
          //have been handled
          PostMessage(g_dc, WM_ProcessNextItem, 0, 0); 
      end;
   else
      DefWindowProc(g_dc, Message.Msg, Message.wParam, Message.lParam);
   end;
end;
Run Code Online (Sandbox Code Playgroud)

即使我生成WM_ProcessNextItem 后,我产生一个WM_PAINT消息,(即Invalidate)时,WM_PAINT将永远不会得到处理,因为总是收到其它投送消息.而作为MSDN说,绘制消息只会如果没有其他发布的消息出现.

更新三:是的,只有消息队列,但这就是我们不关心的原因:

发送和发布的消息

我将在这里使用的术语是非标准的,但我正在使用它,因为我认为它比标准术语更清晰.出于本讨论的目的,我将说明与线程相关的消息分为三个桶而不是更标准的两个桶:

What I'll call them            Standard terminology
===========================    =============================
Incoming sent messages         Non-queued messages
Posted messages            \_  
Input messages             /   Queued messages
Run Code Online (Sandbox Code Playgroud)

实际上,消息细分比这更复杂,但我们暂时坚持使用上述模型,因为它"足够真实".

旧的新事物,整个Windows演变的实践发展作者:
Raymond Chen
ISBN 0-321-44030-7
版权所有©2007 Pearson Education,Inc.
第15章 - 如何传递和检索窗口消息,第358页

更容易想象有两个消息队列.在"第一个"队列为空之前,不会读取"第二个"中的消息; OP永远不会让第一个队列流失.因此,第二个队列中的"绘制"和"输入"消息都没有被处理,使应用程序看起来挂起.

这是对实际情况的简化,但它足够接近本讨论的目的.

更新四

问题不一定是你用你的消息"淹没"输入队列.只有一条消息,您的应用程序可能无响应.只要队列中有一条已发布的消息,就会在任何其他消息之前处理它.

想象一下发生了一系列事件:

  • 移动鼠标(WM_MOUSEMOVE)
  • 鼠标左键被按下(WM_LBUTTONDOWN)
  • 鼠标左键被释放(WM_LBUTTONUP)
  • 用户将另一个窗口移开,导致您的应用需要重新绘制(WM_PAINT)
  • 您的主题已准备好项目,并发布通知(WM_ProcessNextItem)

您的应用程序的主消息循环(调用GetMessage)将不会按照它们发生的顺序接收消息.它将检索WM_ProcessNextItem消息.这将从队列中删除消息,留下:

  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_PAINT

当您处理项目时,用户会再移动鼠标,并随机点击:

  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_PAINT
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP

为了回应你,WM_ProcessNextItem你发回另一条消息给自己.这样做是因为您希望在继续处理更多项目之前先处理未完成的消息.这将向队列添加另一条发布的消息:

  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_PAINT
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_ProcessNextItem

问题开始变得明显.下一次调用GetMessage将检索WM_ProcessNextItem,为应用程序留下积压的绘制和输入消息:

  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_PAINT
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_MOUSEMOVE
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP

解决方案是利用消息的无序处理.始终在Paint/Input/Timer消息之前处理已发布消息:不使用已发布消息.你可以想到的消息队列为被分为两组:发布消息和输入消息.而不是导致永远不允许"已发布"消息队列被清空的情况:

Posted messages      Input messages
==================   =====================
WM_ProcessNextItem   WM_MOUSEMOVE
                     WM_LBUTTONDOWN
                     WM_LBUTTONUP
                     WM_PAINT
Run Code Online (Sandbox Code Playgroud)

你使用一条WM_TIMER消息:

Posted messages      Input messages
==================   =====================
                     WM_MOUSEMOVE
                     WM_LBUTTONDOWN
                     WM_LBUTTONUP
                     WM_PAINT
                     WM_TIMER
Run Code Online (Sandbox Code Playgroud)

Nitpickers Corner:这两个队列的描述并不严格,但这是真的.Windows如何以记录的顺序传递消息是内部实现细节,可随时更改.

重要的是要注意你没有用计时器进行轮询,这将是不好的.相反,你正在发射一次性定时器作为生成WM_TIMER消息的机制.你这样做是因为你知道定时器消息不会优先于绘制输入消息.

使用计时器具有另一个可用性优势.在WM_PAINT,WM_TIMER输入消息之间,还有对它们的无序处理:

  • 然后输入消息
  • WM_PAINT, 然后
  • WM_TIMER

如果您使用计时器通知主线程,您还可以保证您将更快地处理绘画和用户输入.这可确保您的应用程序保持响应.这是一个可用性增强,你可以免费获得它.

  • 定时器轮询,如果不需要工作则不执行任何操作,需要很少的CPU时间,甚至无法测量它.在事物计划中担心这一点是毫无意义的. (2认同)

Dav*_*nan 5

您将很难找到比 PostMessage 更好的东西。我的猜测是您的问题是您更新 UI 过于频繁,并且您的队列变得饱和,因为您无法足够快地为其提供服务。如果你更新不到一秒钟,跳过更新怎么样?如果这恢复了响应能力,那么您可以考虑更强大的解决方案。