【问题标题】:"window procedure" of a newly created thread without window新创建的没有窗口的线程的“窗口过程”
【发布时间】:2009-11-13 06:40:18
【问题描述】:

我想为一些不应该阻塞用户界面的数据库写入创建一个线程,以防数据库不存在。为了与主线程同步,我想使用 windows 消息。主线程将要写入的数据发送到写线程。

发送没有问题,因为 CreateThread 返回了新创建线程的句柄。我考虑过创建一个标准的 Windows 事件循环来处理消息。但是如何在没有窗口的情况下将窗口过程作为 DispatchMessage 的目标?

标准 windows 事件循环(来自 MSDN):

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

为什么是 Windows 消息?因为它们速度快(windows 依赖于它们)并且是线程安全的。这种情况也很特殊,因为第二个线程不需要读取任何数据。它只需要接收数据,将其写入数据库,然后等待下一个数据到达。但这正是标准事件循环所做的。 GetMessage 等待数据,然后处理数据,一切重新开始。甚至还有一个很好理解的用于终止线程的定义信号 - WM_QUIT。

其他同步构造不时阻塞其中一个线程(临界区、信号量、互斥锁)。至于评论中提到的事件 - 我不知道。

【问题讨论】:

  • 我很好奇,为什么要使用windows消息而不是线程同步对象(如事件)?
  • 通过PostThreadMesssageGetMessage使用windows消息相对容易,因为你可以传递一些参数。

标签: multithreading winapi


【解决方案1】:

这似乎有悖常理,但对于没有窗口的消息,实际上使用窗口 proc 创建隐藏窗口比在消息泵中手动过滤 GetMessage() 的结果要好。

你有一个HWND 的事实意味着只要正确的线程有一个消息泵,消息就会被路由到某个地方。考虑到许多函数,甚至是内部的 Win32 函数,都有自己的消息泵(例如MessageBox())。 MessageBox() 的代码不会知道在 GetMessage() 之后调用您的自定义代码,除非有 DispatchMessage() 知道的窗口句柄和窗口过程。

通过创建隐藏窗口,您会被线程中运行的任何消息泵所覆盖,即使它不是您编写的。

编辑:但不要只相信我的话,请查看 Microsoft 的 Raymond Chen 的这些文章。

【讨论】:

  • 不,它是一个手柄。具体的线程句柄。您可以使用它通过 WaitForSingleObject() 等“加入”线程,并查询线程例程的返回值......您也可以使用它来发送消息,但这依赖于有一个消息泵,它将处理它,它有我描述的弱点。
  • 您不能使用线程的 HANDLE 向线程发布消息。您必须改用线程的 ID,它也由 CreateThread() 返回。 PostThreadMessage() 将 ID 而非 HANDLE 作为输入。
  • 与所有 RC 建议(以及一般建议)一样,了解建议的上下文很重要(正如 RC 指出的那样)。因此,如果您正在设计一个非 UI 工作线程,然后决定使用线程消息将通信序列化到该线程,那么:使用 PostThreadMessage 并不是不合适的。该设计已经排除了 RC 提到的禁忌症。您可能会遇到的唯一模态消息循环将由 ASSERT 宏的显示模态对话框创建。 (但是,坦率地说,模态断言对话框无论如何都会在常规 UI 线程中导致如此多的重入问题!)
【解决方案2】:

注意:仅当您不需要任何类型的 UI 相关或某些 COM 相关代码时才参考此代码。除了这种极端情况,这段代码可以正常工作:特别适用于纯计算受限的工作线程。

如果线程没有窗口,则不需要DispathMessageTranslateMessage。所以,干脆忽略它。 HWND 与您的场景无关。您实际上根本不需要创建任何窗口。请注意,需要两个 *Message 函数来处理与 Windows-UI 相关的消息,例如 WM_KEYDOWN 和 WM_PAINT。

我也更喜欢 Windows 消息使用 PostThreadMessageGetMessagePeekMessage 在线程之间同步和通信。我想从我的代码中剪切和粘贴,但我只是简单地勾勒一下这个想法。

#define WM_MY_THREAD_MESSAGE_X   (WM_USER + 100)
#define WM_MY_THREAD_MESSAGE_Y   (WM_USER + 100)


// Worker Thread: No Window in this thread
unsigned int CALLBACK WorkerThread(void* data)
{
  // Get the master thread's ID
  DWORD master_tid = ...;
  while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
  { 
    if (bRet == -1)
    {
      // handle the error and possibly exit
    }
    else
    {
      if (msg.message == WM_MY_THREAD_MESSAGE_X)
      {
        // Do your task

        // If you want to response,
        PostThreadMessage(master_tid, WM_MY_THREAD_MESSAGE_X, ... ...);
      }

      //...
      if (msg.message == WM_QUIT)
        break;
    }
  }
  return 0;
}


// In the Master Thread
//
// Spawn the worker thread
CreateThread( ... WorkerThread ... &worker_tid);

// Send message to worker thread
PostThreadMessage(worker_tid, WM_MY_THREAD_MESSAGE_X, ... ...);

// If you want the worker thread to quit
PostQuitMessage(worker_tid);

// If you want to receive message from the worker thread, it's simple
// You just need to write a message handler for WM_MY_THREAD_MESSAGE_X
LRESULT OnMyThreadMessage(WPARAM, LPARAM)
{
  ...
}

我有点担心这就是你想要的。但是,我认为代码很容易理解。通常,创建线程时没有消息队列。但是,一旦调用了 Window-message 相关函数,线程的消息队列就会被初始化。请注意,再次发布/接收 Window 消息不需要 Window。

【讨论】:

  • 根据我在回答中引用的一篇文章,它不仅仅是具有模式消息泵的 GUI 代码。 COM 同步也可以。此外,仅仅因为您不创建窗口并不意味着您调用的所有函数都是如此,包括那些您无法控制的函数。最好进行防御性编码并使用隐藏窗口技术。
  • 我已经看过陈的文章了。但是,根据我的经验,COM 相关的模态循环很少会吃掉PostThreadMessage 发布的消息。但是,您在技术上是正确的。我将编辑代码。
  • 您为新用户消息 OnMyThreadMessage() 编写了一个处理程序。但是它在哪里被调用,它是如何注册为回调的(特别是如果消息泵没有在您的代码中明确显示,例如使用应用程序框架中的事件循环)?
【解决方案3】:

除非线程有实际的窗口要管理,否则您的线程中不需要窗口过程。一旦线程调用了 Peek/GetMessage(),它就已经拥有窗口过程将接收到的相同消息,因此可以立即对其进行操作。只有在涉及实际窗口时才需要发送消息。发送您不关心的任何消息是一个好主意,以防您的线程使用的其他对象在内部有自己的窗口(例如,ActiveX/COM 有)。例如:

while( (bRet = GetMessage(&msg, NULL, 0, 0)) != 0 )
{
    if (bRet == -1)
    {
      // handle the error and possibly exit
    }
    else
    {
        switch( msg.message )
        {
            case ...: // process a message
                ...
                break;
            case ...: // process a message
                ...
                break;
            default: // everything else
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                break;
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-18
    • 2015-09-20
    • 1970-01-01
    • 1970-01-01
    • 2014-11-15
    • 2021-09-03
    相关资源
    最近更新 更多