Search code examples
winapisendmessagewaitformultipleobjects

PostMessage with WM_USER doesn't seem to arrive when MsgWaitForMultipleObjectsEx is used to check for it


I have a program with a few thread loops that you can post tasks to. One of these thread loops is the UI thread loop. It has to handle window messages as well as the posted tasks, so I send WM_USER messages to wake the thread in the dispatch loop.

The problem is that sometimes (specifically when there's lot of other window messages like WM_PAINT or WM_RESIZE) my WM_USER message doesn't wake the thread. It seems that the PostMessage function doesn't wake the thread from the MsgWaitForMultipleObjectsEx call, though I can't figure out why.

This is what it looks like (some paraphrasing for simplicity):

#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;
}

Edit: The direct call to MsgWaitForMultipleObjectsEx above was a simplification. I actually call a function that looks like this:

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();
        }               
    }
}

Solution

  • I have found the culprit now, and it seems that in some cases messages are dispatched from the queue by Windows outside of the message loop (i.e. they are sent to WindowProcedure automatically). To solve this I changed my WindowProcedureto be like this:

    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);
    

    Thanks everyone for your help and suggestions!