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

StackOverflow https://stackoverflow.com/questions/14998094

سؤال

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();
        }               
    }
}
هل كانت مفيدة؟

المحلول 3

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!

نصائح أخرى

When MsgWaitForMultipleObjects[Ex] says that it returned due to one or more messages, you must go into a loop processing all of them. Your code processes only one message, which means that the second message remains unprocessed. That's why you never get your WM_USER message: You gave up before you got a chance to see it.

Not sure if it is the culprit in your case, but you should organize the code so the PostMessage() is guaranteed to be used after the target thread already has its message loop.

New threads do not initially have any message queue, and it is only created after a first call attempting to get a message from it. I am not sure if MsgWaitForMultipleObjectsEx() counts here, so I would recommend to begin the thread with a call to PeekMessage(), just in order to create the queue.

Your app should guarantee that it never posts/sends messages to the thread before the PeekMessage() returns, or the message can simply get lost.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top