17
votes

I have an application which second thread calls GetMessage() in a loop. At some point the first thread realizes that the user wants to quit the application and notifies the second thread that it should terminate. As the second thread is stuck on GetMessage(), the program never quits. Is there a way to wait for messages with a timeout? I’m open to other ideas too.

EDIT: (additional explanations)

The second thread runs that snippet of code:

while ( !m_quit && GetMessage( &msg, NULL, 0, 0 ) )
{
    TranslateMessage( &msg );
    DispatchMessage( &msg );
}

The first thread sets m_quit to true.

5
Wait... which thread is in the loop waiting on GetMessage()? The first thread, the second thread, or both? - templatetypedef
The second thread runs the ˇGetMessageˇ loop. Should I post some code? - qdii
Code always helps. I'm just confused because your question makes it seem like both threads are blocked. Can't you just send a custom message saying "you need to shut down now?" - templatetypedef
The question is interestiong, but I don't see why you need it. Once the main thread is willing to quit, it just have to notify all the thread to finish (PostQuitMessage is your friend here), and then wait for them to be finished (WaitForSingleObject, WaitForMultipleObjects) and then finish itself. - rodrigo
@rodrigo mostly because I’m not savvy when it comes to the Windows API :) if this is the recommended way to do things, I will change to that. - qdii

5 Answers

21
votes

Not tested, but you could try the function MsgWaitForMultipleObjects without actually any object.

MsgWaitForMultipleObjects(0, NULL, FALSE, timeout, QS_ALLINPUT);

If if returns WAIT_TIMEOUT it is a timeout, but if it returns WAIT_OBJECT_0 you can call GetMessage with guarantee not to be blocked.

But note the following:

MsgWaitForMultipleObjects does not return if there is unread input of the specified type in the message queue after the thread has called a function to check the queue.

So you have to ensure that the last time you called any of the message functions there is no messages left on the queue, or you will have a kind of race condition.

Probably your best option would be to replace GetMessage with:

if (MsgWaitForMultipleObjects(0, NULL, FALSE, timeout, QS_ALLINPUT) == WAIT_OBJECT_0)
{
    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
        //dispatch the message
    }
}

But as I said before, I didn't test it so I cannot be sure if it will work.

19
votes

The easiest way is to just call UINT_PTR timerId=SetTimer(NULL, NULL, 1000, NULL) before calling GetMessage. It will post a WM_TIMER message to the calling thread every second, so the GetMessage will return promptly. Then, call KillTimer(NULL, timerId) to cancel it.

UPDATE Sample code:

BOOL GetMessageWithTimeout(MSG *msg, UINT to)
{
    BOOL res;
    UINT_PTR timerId = SetTimer(NULL, NULL, to, NULL);
    res = GetMessage(msg);
    KillTimer(NULL, timerId);
    if (!res)
        return FALSE;
    if (msg->message == WM_TIMER && msg->hwnd == NULL && msg->wParam == timerId)
        return FALSE; //TIMEOUT! You could call SetLastError() or something...
    return TRUE;
}
4
votes

One thing you can always do is just send the blocked thread a user-defined message that will cause it to wake up, process the message, then go back up to the top of the loop. In fact, it might be easiest to completely remove the m_quit variable and instead to just send the main thread a message that says "you need to quit now."

Hope this helps!

3
votes

You should be able to post a quit message to the second thread with PostThreadMessage.

E.g.

PostThreadMessage(threadid, WM_QUIT, 0, 0);

You shouldn't need to read the m_quit variable in the second thread but you should check for errors from GetMessage as well as a return value of FALSE/0 which is what is returned if the next message is a quit message.

-1
votes

Yep. Try PeekMessage() instead. But I think this will not be full problem solving.