2

我有一个带有几个线程循环的程序,您可以将任务发布到其中。这些线程循环之一是 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();
        }               
    }
}
4

3 回答 3

3

MsgWaitForMultipleObjects[Ex]说它由于一条或多条消息而返回时,您必须进入一个循环处理所有这些消息。您的代码只处理一条消息,这意味着第二条消息仍未处理。这就是为什么你永远不会得到你的WM_USER信息:你在有机会看到它之前就放弃了。

于 2013-02-21T13:24:18.027 回答
0

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

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

你的应用程序应该保证它在PeekMessage()返回之前永远不会向线程发布/发送消息,否则消息可能会丢失。

于 2013-02-21T09:41:07.647 回答
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);

感谢大家的帮助和建议!

于 2013-03-01T09:20:03.223 回答