当MsgWaitForMultipleObjectsEx用于检查时,带有WM_USER的PostMessage似乎没有到达

时间:2013-02-21 08:55:42

标签: winapi sendmessage waitformultipleobjects

我有一个带有几个线程循环的程序,您可以将任务发布到。其中一个线程循环是UI线程循环。它必须处理窗口消息以及发布的任务,所以我发送WM_USER消息来唤醒调度循环中的线程。

问题在于,有时(特别是当有很多其他窗口消息,如WM_PAINTWM_RESIZE)时,WM_USER消息不会唤醒线程。似乎PostMessage函数不会从MsgWaitForMultipleObjectsEx调用中唤醒线程,但我无法弄清楚原因。

这就是它的样子(为简单起见,有些解释):

#define HaveWorkMessage (WM_USER + 100)

class ThreadLoopUI {
public:
    ThreadLoopUI()
        : myHaveWork(0) {}

    void PostTask(Task& aTask) {
        {
            ScopedLock lock(myMutex);
            myTaskQueue.push_back(aTask);
        }

        ScheduleWork();
    }

    void ScheduleWork() {
        if (InterlockedExchange(&myHaveWork, 1)) {
            // No need to spam the message queue
            return;
        }

        if (!PostMessage(myHWnd, HaveWorkMessage, reinterpret_cast<WPARAM>(this), 0)) {
            std::cerr << "Oh noes! Could not post!" << std::endl;
        }
    }

    void Run() {
        for (;;) {
             // SIMPLIFICATION, SEE EDIT BELOW
             DWORD waitResult = MsgWaitForMultipleObjectsEx(0, NULL, (DWORD)INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE);

             if (waitResult == WAIT_FAILED) {
                  std::cerr << "Well, that was unexpected..." << std::endl;
                  continue;
             }

             bool doWork = false;

             MSG message;
             if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {

                   if (message == HaveWorkMessage) {
                        doWork = true;
                        InterlockedExchange(&myHaveWork, 0);
                   }

                   // Send the message on to the window procedure
                   TranslateMessage(&message);
                   DispatchMessage(&message);
             }

             if (doWork) {
                 // Process all tasks in work queue
             }
        }
    }
private:
    HWND                 myHwnd;
    Mutex               myMutex;
    std::vector<Task>   myTaskQueue;
    LONG volatile       myHaveWork;
}

修改:以上对MsgWaitForMultipleObjectsEx的直接调用是一种简化。我实际上调用的函数看起来像这样:

void WaitForMessages() {
    DWORD waitResult = MsgWaitForMultipleObjectsEx(0, NULL, (DWORD)INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE);

    if (waitResult == WAIT_OBJECT_O) {
        // Comment from the Chromium source:
        // A WM_* message is available.
        // If a parent child relationship exists between windows across threads
        // then their thread inputs are implicitly attached.
        // This causes the MsgWaitForMultipleObjectsEx API to return indicating
        // that messages are ready for processing (Specifically, mouse messages
        // intended for the child window may appear if the child window has
        // capture).
        // The subsequent PeekMessages call may fail to return any messages thus
        // causing us to enter a tight loop at times.
        // The WaitMessage call below is a workaround to give the child window
        // some time to process its input messages.
        MSG message = {0};
        DWORD queueStatus = GetQueueStatus(QS_MOUSE);
        if (HIWORD(queueStatus) & QS_MOUSE &&
            !PeekMessage(&message, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) 
        {
            WaitMessage();
        }               
    }
}

3 个答案:

答案 0 :(得分:3)

MsgWaitForMultipleObjects[Ex]表示由于一条或多条消息而返回时,您必须go into a loop processing all of them。您的代码只处理一条消息,这意味着第二条消息仍未处理。这就是为什么你永远不会收到WM_USER消息的原因:在你有机会看到它之前你放弃了。

答案 1 :(得分:0)

不确定它是否是您的情况的罪魁祸首,但您应该组织代码,以便在目标线程已经有其消息循环后保证使用PostMessage()

新线程最初没有任何消息队列,只有在第一次尝试从中获取消息后才会创建它。我不确定MsgWaitForMultipleObjectsEx()是否在这里计算,所以我建议通过调用PeekMessage()来开始该线程,只是为了创建队列。

您的应用应保证在PeekMessage()返回之前永远不会向帖子发帖/发送消息,否则消息就会丢失。

答案 2 :(得分:0)

我现在找到了罪魁祸首,似乎在某些情况下,消息循环之外的Windows会从队列中分派消息(即它们会自动发送到WindowProcedure)。为了解决这个问题,我将WindowProcedure更改为:

LRESULT CALLBACK 
ThreadLoopUI::WindowProcedure( 
    HWND    aWindowHandle, 
    UINT    aMessage, 
    WPARAM  aWParam, 
    LPARAM  aLParam )
{
    switch (aMessage)
    {
    case HaveWorkMessage:
        // This might happen if windows decides to start dispatch messages from our queue
        ThreadLoopUI* threadLoop = reinterpret_cast<ThreadLoopUI*>(aWParam);

        InterlockedExchange(&threadLoop->myHaveWork, 0);

        // Read the next WM_ message from the queue and dispatch it
        threadLoop->PrivProcessNextWindowMessage();

        if (threadLoop->DoWork())
        {
            threadLoop->ScheduleWork();
        }

        break;
    }

    return DefWindowProc(aWindowHandle, aMessage, aWParam, aLParam);

感谢大家的帮助和建议!