【问题标题】:Recursive message loop递归消息循环
【发布时间】:2013-10-12 14:04:17
【问题描述】:

我无法从消息处理程序中正确运行消息循环。实际上复制 DialogBox() 处理消息的方式,减去所有窗口。

在消息处理程序中简单地调用 GetMessage() 几乎可以工作,除非打开系统菜单的 WM_SYSKEYDOWN 事件也触发进入子循环。在发生这种奇怪的事情之后,按键被吞下,与系统菜单相关的 WM_MOUSEMOVE 消息被发送到主窗口。

据记录,这在 Windows 8 和 XP 中都会发生。

为了给出一些上下文,我正在尝试一个线程模型,其中(无窗口)工作线程通过阻塞 SendMessage 调用回作为服务器的主窗口进行通信。这些操作可能需要进一步输入或依赖于其他 I/O,因此需要处理常规消息,直到准备好回复。

我相当肯定这是我的一个基本错误或误解,就像我上次在这里发帖一样,但我似乎无法完全弄清楚我自己做错了什么。

这是我的复制案例。按ALT+SPACE打开系统菜单后尝试导航,

#include <windows.h>

BOOL update;

LRESULT WINAPI WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    MSG msg;
    char text[256];
    switch(uMsg) {
    case WM_DESTROY:
        ExitProcess(0);
    // Trigger an update on input
    case WM_SYSKEYDOWN:
        update = TRUE;
        break;
    // Display the update from the worker thread, returning once it is time to
    // ask for the next one
    case WM_USER:
        wsprintf(text, TEXT("%u"), (unsigned int) lParam);
        SetWindowText(hwnd, text);
        while(!update && GetMessage(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        update = FALSE;
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(void *hwnd) {
    // Submit updates as quickly as possible
    LONG sequence = 1;
    for(;;)
        SendMessage(hwnd, WM_USER, 0, sequence++);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCommandLine, int nCmdShow) {
    HWND hwnd;
    MSG msg;

    // Create our window
    WNDCLASS windowClass = { 0 };
    windowClass.lpfnWndProc = WindowProc;
    windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    windowClass.hCursor = NULL;
    windowClass.lpszClassName = TEXT("Repro");
    RegisterClass(&windowClass);
    hwnd = CreateWindow(TEXT("Repro"), TEXT("Repro"),
        WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
        hInstance, 0);
    // Launch the worker thread
    CreateThread(NULL, 0, ThreadProc, hwnd, 0, NULL);
    // And run the primary message loop
    while(GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

【问题讨论】:

  • 不要做那种事,你只会一个接一个地遇到问题。如果你想要线程合作,你可以使用 PostMessage/PostThreadMessage(在工作线程中带或不带对象 Window)
  • 谢谢,我会试试这个解决方法。这么多年过去了,我还是不明白消息队列的操作,这让我很担心。你能告诉我为什么这会失败或我违反了合同的哪一部分吗?
  • 我不知道,可能与在之前的 DispatchMessage() 仍在运行时运行新的消息循环有关。就个人而言,我会通过CreateEvent() 使用等待事件。查看SetEvent()ResetEvent()PulseEvent()WaitForSingleObject()等相关函数。
  • Remy Lebeau:这就是我的想法。尽管如此,MessageBox 和其他模态对话框似乎可以毫无问题地管理这个技巧。而且,是的,事件似乎确实比使用 PostThreadMessage 并希望工作线程永远不会显示任何 UI,或同步虚拟窗口的创建/拆除而不是停止广播更可靠。
  • 我发现使用 PostMessage 进行异步编程要简单得多。经过数小时用“event”、“set”和“wait”调试奇怪的同步错误和锁,你会发现 PostMessage 非常令人耳目一新。

标签: winapi asynchronous modal-dialog message-queue systemmenu


【解决方案1】:

模态消息循环非常好。 Raymond Chen 有一个series of articles on writing modal message loops properly

我注意到一件事:您的主题应该发布消息,而不是发送; SendMessage 直接调用窗口进程。也不要使用PostThreadMessage;这是为没有可见 UI 的线程设计的(以及 nested DispatchMessage won't know how to dispatch the thread message,导致消息丢失)。

【讨论】:

  • 谢谢,我会仔细看看那篇文章。在这种情况下,我有意使用 SendMessage,因为我希望调用阻塞,直到窗口完成处理消息,如果我正确理解了事情,跨线程应该没问题。当我不需要等待时,我确实使用 PostMessage 代替(实际上是 SendNotifyMessage,因为它们是按顺序交付的。)
  • @doynax - 如果您需要阻止,请使用事件。 Cross-thread SendMessage often leads to deadlockother odd failures.
  • 我希望我能在无窗口线程上摆脱它,但也许还有一些我没有考虑过的边缘情况。 SendMessage 作为一种阻塞机制相当方便,用于接收结果并能够传递本地参数块结构。
  • 您链接的文章显示我必须在嵌套循环中重新发布 WM_QUIT。不幸的是,我仍然没有诊断出根本问题,而是放弃并按照建议使用事件解决它(自我注意:关闭事件对象不会导致返回 WAIT_ABANDONED。)
猜你喜欢
  • 1970-01-01
  • 2022-01-23
  • 2018-06-27
  • 2017-01-21
  • 2012-07-22
  • 1970-01-01
  • 2013-08-12
  • 2011-02-09
  • 2023-04-02
相关资源
最近更新 更多