无法删除WM_TIMER消息

Can't Remove WM_TIMER message

提问人:lequinne 提问时间:4/16/2016 最后编辑:lequinne 更新时间:4/16/2016 访问量:726

问:

我的代码有问题,几个小时后,我似乎无法弄清楚......

问题:我正在尝试每十秒尝试连接到服务器一次。当计时器第一次经过时,回调函数被调用得刚刚好,但随后立即再次调用(无需等待十秒钟),并且它不断被反复调用,就好像计时器消息没有从队列中删除一样。谁能帮忙?

计时器设置如下:

SConnect::SConnect()
{
    hSimConnect = NULL;
    Attempt();
    SetTimer(hMainWindow, reinterpret_cast<UINT_PTR>(this), 10000, (TIMERPROC)TimerProc);
}

(唯一的)应用程序消息循环如下:

    while (true)
    {
        PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
        if (msg.message == WM_QUIT)
            break;

        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        //UPDATES
        pSCObj->Update();
        manager.Update();
        Sleep(50);
    }

定时器回调函数在这里:

void CALLBACK SConnect::TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime){
    SConnect *pSC = reinterpret_cast<SConnect *>(idEvent);
    MSG wMsg;

    if (!pSC->connected)
    pSC->Attempt();
    else{
        pSC->connected = false;
    }
}

我真的很感激任何帮助......如果您需要更多信息,请告诉我...

真诚地 法

C++ Windows WinAPI 计时器

评论

3赞 Frédéric Hamidi 4/16/2016
这些计时器是周期性的。你必须呼吁他们停下来。KillTimer()
0赞 lequinne 4/16/2016
哦,对不起,我的问题不清楚。我的意思是立即一次又一次地调用回调(不是每十秒)。
0赞 David Heffernan 4/16/2016
请提供一个最小的可重复示例,从而避免任何混淆的余地
6赞 Hans Passant 4/16/2016
您的消息循环中有一个错误,它不检查 PeekMessage() 的返回值。因此,您获得的第一个WM_TIMER将起作用。然后 PeekMessage() 返回 FALSE,您看不到该错误,您将一遍又一遍地调度消息。顺便说一句,Sleep(50) 调用也非常邪恶。
1赞 Raymond Chen 4/17/2016
这意味着您的程序每秒最多处理 20 条消息。这也意味着需要 50 毫秒才能响应任何新消息。您永远不应该在 UI 线程上。如果要更新 50 毫秒,请创建另一个计时器来触发更新。Sleep()Sleep

答:

2赞 Andy 4/16/2016 #1

我认为发生这种情况是因为您正在调用消息循环而不是.PeekMessage()GetMessage()

PeekMessage()如果队列中没有消息,将返回。我不知道的实现是什么,但如果队列中没有消息,我可以看到它不理会参数的内容。这意味着当返回时,将包含队列中的上一条消息。 然后盲目地传递给 ,然后 尽职尽责地将其传递给窗口的窗口过程。因此,只要该消息是最后处理的消息,就会调用您的计时器回调,直到将另一条消息添加到队列中。FALSEPeekMessage()msgPeekMessage()FALSEmsgmsgDispatchMessage()WM_TIMER

您可以使用更传统的消息循环来解决此问题:

BOOL bRet;

while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }

    pSCObj->Update();
    manager.Update();
}

(消息循环改编自 GetMessage() 文档中的示例。

由于在队列中有消息之前会阻塞,因此您不需要调用 ,并且您的进程在无事可做时不会使用任何 CPU。GetMessage()Sleep()

评论

0赞 Matthieu 4/16/2016
我总是在我所有的程序中使用相同的消息循环,所以我甚至不再关注它:)不过,这应该是一个很好的警告。+1Sleep(50)
2赞 Hans Passant 4/16/2016
请记住,这不是一个功能替代,Update() 方法的调用还不够。使用 PeekMessage() 是可以的,忽略返回值是不行的。
0赞 Andy 4/16/2016
@Hans这很公平。只是想举一个最小的例子来让提问者开始。
0赞 lequinne 4/16/2016
这段代码工作得很好: // 主消息循环: while (true) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){ if (msg.message == WM_QUIT) break; if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg);DispatchMessage(&msg);} } //更新 pSCObj->Update();经理。更新();睡眠(50);}
0赞 Matthieu 4/16/2016
@lequinne你可以编辑你的帖子以包含适合你的代码?(或为您的问题添加答案)。在评论中发布超过一行代码是很难阅读的(尤其是在未格式化的情况下)。
1赞 lequinne 4/16/2016 #2

问题是没有检查 PeekMessage() 的返回值。在每个循环中,如果没有新消息,PeekMessage 会保持 msg 的内容不变,然后再次调度。

仅当 PeekMessage() 返回 true 时才调度消息可以解决问题:

    while (true)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            if (msg.message == WM_QUIT)
                break;    

            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        //UPDATES
        pSCObj->Update();
        manager.Update();
        Sleep(50);
    }