我们正在为我们的硬件传感器套件实施一些SDK。
成功获得我们的传感器的工作C API后,我们现在开始测试SDK的艰巨任务,以确保我们没有引入任何致命错误,内存泄漏或竞争条件。
我们的一位工程师报告说,在开发测试应用程序(Qt Widgets应用程序)时,挂钩到从DLL中的单独线程执行的回调时会出现问题。
这是回调原型:
#define API_CALL __cdecl
typedef struct {
// msg fields...
DWORD dwSize;
// etc...
} MSG_CONTEXT, *PMSG_CONTEXT;
typedef void (API_CALL *SYS_MSG_CALLBACK)(const PMSG_CONTEXT, LPVOID);
#define API_FUNC __declspec(dllexport)
API_FUNC void SYS_RegisterCallback(SYS_MSG_CALLBACK pHandler, LPVOID pContext);
它附在Qt中如下:
static void callbackHandler(const PMSG_CONTEXT msg, LPVOID context) {
MainWindow *wnd = (MainWindow *)context;
// *wnd is valid
// Call a function here
}
MainWindow::MainWindow(QWidget *parent) {
SYS_RegisterCallback(callbackHandler, this);
}
我的问题是:是在创建它的线程上执行的回调还是在执行它的线程上执行的回调?在任何一种情况下,我想它需要某种同步方法。谷歌搜索已经产生了大量的C#示例,但这并不是真正需要的。
正在考虑的一件事是使用SendMessage
或PostMessage
函数,而不是沿着回调路径。
有人可以提供任何建议吗?如何使用回调实现跨线程安全?或者消息泵路由是基于Windows的SDK的方式吗?
答案 0 :(得分:1)
我的问题是:是在创建它的线程上执行的回调还是在执行它的线程上执行的回调?
执行它的线程。
有人可以提供任何建议吗?如何使用回调实现跨线程安全?
由互斥锁保护的简单std::queue<message>
是最简单的解决方案。 Here就是一个很好的例子。
让回调除了将消息排入队列之外什么也不做,并从主线程中消耗它。
答案 1 :(得分:1)
有人可以提供任何建议吗?如何使用回调实现跨线程安全?
是 - 它在Qt中完成的标准方式:从任何线程发出信号。 connect
一个插槽/仿函数,它在生活在所需目标线程中的对象的上下文中执行。我们也可以做一些明确没有使用信号/槽的东西,但功能相同 - 毕竟,插槽/仿函数调用是在QMetaCallEvent
的线程中进行的。
您显示的代码通常会导致未定义的行为,因为您尝试从主线程以外的任何线程使用GUI对象(MainWindow
),并且它不太可能你调用的方法是线程安全的。
正确的方法是使MainWindow
方法成为线程安全的,或者以线程安全的方式调用它。
下面显示了一种可能的方法;有关isSafe
和postCall
实施的信息,请参阅this answer。
static void callbackHandler(const PMSG_CONTEXT msg, LPVOID context) {
auto wnd = reinterpret_cast<MainWindow*>(context);
if (!isSafe(wnd))
return postCall(wnd, [=]{ callbackHandler(msg, context); });
// we're executing in wnd's thread context, any calls on wnd
// will be thread-safe
wnd->foo();
}
MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
SYS_RegisterCallback(callbackHandler, this);
}
另请参阅this question about invoking functors across threads和this question about invoking methods thread-safely。
另一种解决方案是直接提供此功能,并且SYS_RegisterThreadPumpCallback
可以像SYS_RegisterCallback
一样注册回调,但会假设接收线程运行消息泵(如Qt&#39) ; s主线程,以及任何QEventLoop
- 旋转线程也是如此。回调将作为消息传递给隐藏窗口,wndProc
然后通过函数指针执行实际调用。
另一个解决方案是SYS_RegisterThreadAPCCallback
,当接收线程可警告时,它将使用QueueUserAPC
来调用回调。这比消息传递到消息泵要好一些,但是如果用户代码在意外的位置警报并因此意外地重新输入不可重入的代码(注意:重入和线程安全是正交概念),则会导致麻烦。 / p>
我个人欢迎在Windows上提供所有三种回调的API。