由于后台线程上的事件而在ui线程上执行方法

时间:2010-09-24 01:55:59

标签: c++ user-interface winapi asynchronous

我有一个后台线程正在轮询服务器。当有数据时,我想处理UI线程上的数据。如果我存储主窗口的hwnd

如何在UI线程上执行特定方法static void DataHandler(void* data)

我认为创建一个传递hwnd和函数指针的计时器会起作用。但有更好的方法吗?我可以使用PostMessage以某种方式调用datahandler。

另外,我不是在编写UI代码,因此我无法修改消息循环中的任何内容。

3 个答案:

答案 0 :(得分:25)

我经常使用两种主要方法在线程之间进行通信。

1)PostMessage()

创建自定义窗口消息,ala:

#define WM_YOU_HANVE_DATA WM_USER + 101

创建一个自定义数据类型,用于保存要发送到主线程以进行处理的数据:

struct MyData
{
  string client_;
  string message_type_;
  string payload_;
};

从你的工作线程中,在堆上实例化MyData 的副本,填充它,并将其发送到主线程:

MyData* data = new MyData;
data->client_ = "hoser";
// ... etc
PostMessage(main_wnd_handle, WM_YOU_HAVE_DATA, reinterpret_cast<WPARAM>(data), );

在主线程中,处理此消息并以适当的方式处理数据。

BEGIN_MESSAGE_MAP(MyAppWindow, CDialogEx)
        // ...  stuff's going to already be here
        ON_MESSAGE(WM_YOU_HAVE_DATA, OnYouHaveData)
END_MESSAGE_MAP()

// ...

一个重要的注意事项:MyAppWindow的主线程现在拥有 MyData*指向的内存,所以你必须拥有它。我在这里使用auto_ptr执行此操作:

LRESULT MyAppWindow::OnYouHaveData(WPARAM wp, LPARAM )
{
  auto_ptr<MyData> data(reinterpret_cast<MyData*>(wp));
  DisplayeClient(data->client_);
  // etc
  return 0;  
}

这可能是最简单的方法,它在线程安全的意义上也很强大。因为您将数据的所有权传递给主线程,所以没有争用。

这种方法的最大缺点是规模限制。这依赖于Windows消息泵来在线程之间移动数据。几乎总是,这不是问题。但是Windows消息队列可以处理的消息数量有限制:

  

每个邮件队列的发布邮件数量限制为10,000个。

reference

同样,对于大多数应用程序来说,这没有问题。

2)QueueUserAPC()

异步过程调用(APC)是在特定线程的上下文中异步执行的函数。 (Link)如果有一个函数ProcessIncomingData()你想在主线程上执行,但你想从一个工作线程触发它,你可以使用{以相当直接的方式调用该函数{1}}。

QueueUserAPC()方法一样,您从在堆上实例化的自定义数据类型开始:

PostMessage()

定义用户APC,记住取得传入数据的所有权

struct MyData
{
  string client_;
  string message_type_;
  string payload_;
};

// ...

MyData* data = new MyData;
data->client_ = "hoser";

然后排队异步程序调用。使用VOID CALLBACK ProcessIncomingData(ULONG_PTR in) { auto_ptr<MyData> data(reinterpret_cast<MyData*>(in)); // magic happens } 方法,您需要主线程的窗口HWND。在这里,您需要主线程的实际线程HANDLE。

PostMessage()

有一个很大的警告。为了让主线程调用APC,主线程必须处于可警告的等待状态。当您调用其中一个WaitEx()函数(如WaitForMultipleObjectsEx()并将“alertable”标志设置为true)时,您将进入可警告的等待状态。

问题是GUI线程几乎从不应该处于可警告的等待状态,因为你几乎不应该等待。等待主线程将阻止消息泵,使您的应用程序似乎冻结。这真是太糟了。我将此方法包含在内以便完整 - 您经常需要在两个工作(非GUI)线程之间进行通信,这通常是最有效的方法。

答案 1 :(得分:0)

您可以做的一件事 - 使用线程间信令对象,可能就像布尔标志一样简单。当数据出现在服务器轮询线程上时,您可以发信号通知该标志。您可以在UI线程的消息循环中检查此标志。或者,您可以向UI线程发送自定义窗口消息。

现在我重新阅读了您的问题 - 因为您无法更改UI代码,这种方法无效。您可以使用WIN32 API添加自己的自定义消息挂钩函数来解决此问题。

答案 2 :(得分:0)

我使用的方法(需要修改UI线程,但我认为所有方法都在某些方面)是定义一个自定义消息ID,它在UI线程中处理。消息格式和处理封装在一个类中,因此主UI线程只需要将消息转发给类处理程序,而不知道特定格式是什么。

之后,这是一个简单的问题,即封装任意函数调用(我使用动态分配的boost :: function对象,但还有其他选项),使用自定义消息ID将其传递给主窗口线程,并且应该在主线程的上下文中调用handler。正如您所指出的,Creating custom message types in win32?有关于自定义消息部分的详细信息;只要确保传递的任何数据/对象在堆上分配并在处理函数内释放,如果它将是异步调用(例如:PostMessage)。

更新:John在选项1中所说的,除了在UI中的处理程序调用的函数中编写处理程序操作,因此您对UI代码的影响最小,如果您更改数据结构,那么以任何方式传递,您不需要更新UI代码。这是我唯一的补充建议。

希望有所帮助。