PostMessage с WM_USER, кажется, не прибывает, когда MsgWaitForMultipleObjectsEx используется, чтобы проверить это
У меня есть программа с несколькими циклами потоков, в которые можно публиковать задания. Одним из этих потоковых циклов является цикл потока пользовательского интерфейса. Он должен обрабатывать как оконные сообщения, так и опубликованные задачи, поэтому я посылаю сообщения WM_USER, чтобы разбудить поток в цикле отправки.
Проблема в том, что иногда (особенно когда есть много других оконных сообщений, таких как WM_PAINT
или же WM_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();
}
}
}
3 ответа
Я нашел виновника сейчас, и кажется, что в некоторых случаях сообщения отправляются из очереди 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);
Спасибо всем за вашу помощь и предложения!
Когда MsgWaitForMultipleObjects[Ex]
говорит, что оно вернулось из-за одного или нескольких сообщений, вы должны войти в цикл обработки всех их. Ваш код обрабатывает только одно сообщение, что означает, что второе сообщение остается необработанным. Вот почему вы никогда не получите свой WM_USER
сообщение: Вы сдались, прежде чем вы получили шанс увидеть это.
Не уверен, что это виновник в вашем случае, но вы должны организовать код так, чтобы PostMessage()
гарантированно будет использоваться после того, как целевой поток уже имеет свой цикл сообщений.
Новые потоки изначально не имеют никакой очереди сообщений, и она создается только после первого вызова, пытающегося получить от него сообщение. Я не уверен, если MsgWaitForMultipleObjectsEx()
здесь имеет значение, поэтому я бы порекомендовал начать поток с вызова PeekMessage()
Просто для того, чтобы создать очередь.
Ваше приложение должно гарантировать, что оно никогда не публикует / отправляет сообщения в ветку до PeekMessage()
возвращается, или сообщение может просто потеряться.