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