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

问题描述 投票:3回答:3

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

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

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

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

c++ user-interface winapi asynchronous
3个回答
0
投票

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

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


25
投票

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

1)PostMessage()

创建自定义Windows消息,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。

(Qazxswpoi)

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

2)QueueUserAPC()

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

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()函数(如HANDLE main_thread = my_thread_params.main_thread_handle_; QueueUserAPC(ProcessIncomingData, main_thread, reinterpret_cast<ULONG_PTR>(data)); 并将“alertable”标志设置为true)时,您将进入可警告的等待状态。

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


0
投票

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

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

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

希望有所帮助。

© www.soinside.com 2019 - 2024. All rights reserved.