MFC / WIN32 / C ++:如何在进行计算密集型操作时调度消息?

时间:2011-04-27 17:32:06

标签: c++ visual-studio-2010 winapi mfc

我更愿意看看是否有合理的方法来处理这个,而不使用第二个线程,因为这个应用程序的细节(最初是一个DOS应用程序,有很多全局静态变量,转换为Windows / MFC,但没有设计从一开始就是多线程的 - 只是让它多文档识别是一项重大任务。)

有问题的应用程序正在尝试进行计算密集型操作,这会产生修改当前文档的副作用。它希望能够更新主窗口和在进行处理时迭代地记录窗口。

执行简单的方法:遍历工作项,在文档窗口上发布绘图,修改文档的基础数据,并使用状态文本更新状态栏,指示y完成的x通常有效。但有时应用程序会停止更新状态栏和文档的窗口(视图),直到整个作业完成,然后一切都会立即更新。

因此代码中没有任何挂起条件。即它永远不会完成。这完全是在一段时间内未能处理窗口消息的问题(因为这是一个单线程应用程序,大部分)。

我认为显而易见的方法是在任务循环中放置一个PeekMessage()循环来分派任何累积的消息。我假设这是视觉更新停止发生的原因:视图,主框架或线程的消息队列中必须有一条窗口消息阻止对屏幕的直接更新。

但是,问题可能还有其他问题吗?

无论如何,对于我们的应用来说,忽略消息队列似乎是一个糟糕的主意,因为处理时间可能很长(如果他们因为缺乏而问任务管理器,用户会认为该应用为“停止响应”处理我们的消息队列。)

然而,以下循环变得无限:

    // dispatch the messages until we're out of them again...
    for (MSG msg; PeekMessage(&msg, NULL,  0, 0, PM_REMOVE); )
    {
        TraceMessage(msg.message, msg.wParam, msg.lParam);
        if (msg.message == WM_QUIT)
            return;
    }

注意:TraceMessage()只是向调试器窗口输出一条人性化的跟踪信息&这是争论。在这种情况下,它是WM_PAINT。

因此,即使要求删除它,绘制消息似乎也永远存在于队列中(或者以某种方式无限地生成新的消息)。


问题:

  1. 我是否可以完全错误,以及我们的应用程序无法更新状态栏的原因&视图是别的吗?

  2. 与在任务循环中放置PeekMessage循环相比,是否有更好的方法来处理长cpu或磁盘I / O密集型操作(这不涉及将整个应用程序重新架构为更多线程 - 友好)?


  3. 解决方案我要去......

    void DoSomethingLengthy()
    {
        CWaitCursor wait;
    
        // disable our application windows much as if we were running a modal dialog
        // in order to lock out the user from interacting with us until we're doing doing this thing
        AfxGetMainWnd()->BeginModalState();
    
        while (bMoreWorkToDo)
        {
            // empty any generated / received thread & window messages
            for (MSG msg; PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE|PM_NOYIELD); ) 
                AfxPumpMessage();
    
            // for some reason, our cursor reverts to the normal pointer
            // so force ourselves to continue to have the wait-cursor until we're really done
            AfxGetMainWnd()->RestoreWaitCursor();
    
    
            // here's where we do one work-item
            // ...
        }
    
        // restore our prior state
        AfxGetMainWnd()->EndModalState();
    }
    

    是的,这是一项非常古老的技术,并不一定是最好的方法。然而,对于这种情况,它是可行且非常有用的。我们已经有一个复杂的应用程序,它将OnIdle机制用于多种用途,使其作为一种可能的方法不那么有吸引力。

4 个答案:

答案 0 :(得分:3)

PeekMessage只会查看下一条消息。您希望GetMessage将其从队列中删除,然后DispatchMessage实际调用WndProc

此时你刚刚从1996年技术的主要部分VB重新发明了DoEvents

答案 1 :(得分:2)

您的应用无法更新的原因是因为没有处理WM_PAINT消息。 WM_PAINT的独特之处在于,只要Windows确定窗口的某些部分已过期,它就会按需生成。这就是为什么你得到无限数量的原因。

最好的解决方案是将您的长任务分解成碎片,并使用PostMessage将自定义消息放入队列中,您可以使用该消息继续上次停止。

答案 2 :(得分:2)

其他人已经指出WM_PAINT通常在需要时合成,而不是在消息队列中实际存在WM_PAINT消息。

找到真正的问题,在我看来,实际上只有两种真正的可能性:要么重新构建它,要么不管它。如果你打算单独使用它,最好将它隔离一下,这样你的应用程序就会运行两个几乎完全不同的部分:一个是几乎没有变化的DOS应用程序,就像它一直有的一样。另一个是几乎完全独立的GUI前端。

具体如何操作取决于DOS应用程序如何产生输出。如果它使用标准流(例如,使用printf等写入标准输出),最简单的方法是将其转换为Win32控制台应用程序。让您的GUI应用程序生成控制台应用程序,并将其标准输入,标准输出和标准错误流重定向到父级的匿名管道。然后,父GUI应用程序将(例如)读取子项标准输出中的数据,并相应地更新GUI。

如果,OTOH,你有一个“全屏”DOS程序被编写为直接使用屏幕缓冲区,那么将它作为GUI中相同应用程序的一部分保存会更容易一些。在典型的情况下,这样的程序将具有一些代码来检索指向屏幕的指针,然后将其视为字符/属性对的2D数组。要“捕获”其输出,可以用自己的数组替换硬件屏幕缓冲区。从那里你有两个选择。如果原始代码是用C ++(或与C ++兼容的C语言)编写的,那么虚拟屏幕缓冲区可能会使某些操作符超载,以通知您需要进行更改。否则,您可以每隔(比方说)100毫秒轮询它,并(例如)对内容进行哈希处理,以确定它是否已更改,因此您需要更新GUI。虽然轮询从不声音就像一个好主意,但以100 ms的间隔散列8K数据实际上不太可能导致重大问题。

我会重复一遍:至少IMO,只有两个选择可能会很好:通过重新架构来干净地集成代码,以便让它在一个线程中运行得很好,或者干净利落地分开通过在计算和GUI之间构建干净的通信来构建代码。至少IME,这些极端之间的中间点很少运作良好。您需要分离或集成代码,但无论哪种方式,您都需要彻底和干净地完成。

答案 3 :(得分:1)

只要队列为空并且存在非空的无效区域,就会合成

WM_PAINT。要停止WM_PAINT消息,您必须将窗口区域标记为有效。这种情况发生在正常的油漆处理过程中BeginPaint / EndPaint期间。

the documentation说“从GetMessage或PeekMessage返回内部WM_PAINT消息或由UpdateWindow发送到窗口后,系统不会发布或发送更多WM_PAINT消息,直到窗口失效或直到RedrawWindow为止再次调用RDW_INTERNALPAINT标志设置。“,它假定消息已完全处理。如果您未遵守WM_PAINT处理合同(调用BeginPaintEndPaint等),则不能依赖“不再有WM_PAINT消息”行为。