以下是我遇到问题的概念的不可编辑的代码草图:
struct Data {};
struct A {};
struct B {};
struct C {};
/* and many many more...*/
template<typename T>
class Listener {
public:
Listener(MyObject* worker):worker(worker)
{ /* do some magic to register with RTI DDS */ };
public:
// This function is used ass a callback from RTI DDS, i.e. it will be
// called from other threads when new Data is available
void callBackFunction(Data d)
{
T t = extractFromData(d);
// Option 1: direct function call
// works somewhat, but shows "QObject::startTimer: timers cannot be started
// from another thread" at the console...
worker->doSomeWorkWithData(t); //
// Option 2: Use invokeMethod:
// seems to fail, as the macro expands including '"T"' and that type isn't
// registered with the QMetaType system...
// QMetaObject::invokeMethod(worker,"doSomeGraphicsWork",Qt::AutoConnection,
// Q_ARG(T, t)
// );
// Option 3: use signals slots
// fails as I can't make Listener, a template class, a QObject...
// emit workNeedsToBeDone(t);
}
private:
MyObject* worker;
T extractFromData(Data d){ return T(d);};
};
class MyObject : public QObject {
Q_OBJECT
public Q_SLOTS:
void doSomeWorkWithData(A a); // This one affects some QGraphicsItems.
void doSomeWorkWithData(B b){};
void doSomeWorkWithData(C c){};
public:
MyObject():QObject(nullptr){};
void init()
{
// listeners are not created in the constructor, but they should have the
// same thread affinity as the MyObject instance that creates them...
// (which in this example--and in my actual code--would be the main GUI
// thread...)
new Listener<A>(this);
new Listener<B>(this);
new Listener<C>(this);
};
};
main()
{
QApplication app;
/* plenty of stuff to set up RTI DDS and other things... */
auto myObject = new MyObject();
/* stuff resulting in the need to separate "construction" and "initialization" */
myObject.init();
return app.exec();
};
实际代码中的更多细节:
示例中的Listener
是RTI DataReaderListener,即回调
函数是onDataAvailable()
我正在尝试编写一个使用RTI's Connext DDS进行通信的小型分布式程序,并使用Qt5编写GUI内容 - 但是,我不相信这些详细信息对问题很重要据我所知,归结为以下几点:
myObject
,其线程关联性可能与主GUI线程相关,也可能不同(但为了简单起见,我们假设是这种情况。)doSomeWorkWithData()
表示。免责声明:像往常一样,在开始新项目时,总会有不止一件新事物学到。对我来说,这里的新东西是RTI的Connext和(显然)我自己第一次处理线程。
因此,我不能({随机)调用MyClass::doSomeWorkWithData()
但我需要序列化它。一个(可能很简单)这样做的方法是将事件发布到事件队列myObject
,其中 - 当时间可用时 - 将触发执行所需方法MyClass::doSomeWorkWithData()
情况下。
我已经确认myObject
与上面的示例代码类似地实例化时,与主GUI线程相关联,即myObject.thread() == QApplication::instance()->thread()
。
有了这个,我到目前为止尝试了三个选项:
这种方法基于这样的事实
- myObject
存在于GUI线程中
- 所有创建的侦听器也按原样隶属于GUI线程
由`myObject&#39;创建并以这种方式继承其线程
这实际上导致执行doSomeWorkWithData()
的事实。然而,
其中一些函数操纵QGraphicsItems,只要我得到这种情况
错误消息读取:&#34; QObject :: startTimer:无法从另一个启动计时器
螺纹&#34;
QMetaObject::invokeMethod()
试图通过正确发布myObject
的事件来规避此问题,我
我试图用Q_INVOKABLE
标记MyObject::doSomeWorkWithData()
,但我没有调用它
因为我需要使用Q_ARG
传递参数。我正确注册并声明了我的自定义类型
在示例中由struct A
等表示,但我在事实上失败了
Q_ARG
扩展为包含一个类型的参数的文字,在...中
模板化的案例不起作用("T"
不是注册或声明的类型)。
这种方法基本上直接失败,因为QMeta系统没有 使用模板,即在我看来,根本不可能是任何模板化的QObject。
花了大约一个星期的时间尝试解决这个问题,阅读线程(并在我的代码中发现一些其他问题),我真的很想完成正确的。 因此,我真的很感激,如果:
有人可以向我展示如何通过来自另一个第三方库(或其他任何其他内容)的回调函数从不同的非QThread调用QObject的成员函数的通用方法控制,线程。
有人可以向我解释为什么选项1可以工作,如果我只是不创建一个GUI,即完成所有相同的工作,只是没有QGraphcisScene可视化它(以及项目&#39; s {{ 1}}成为app
而不是QCoreApplication
,并且所有与图片相关的作品QApplication
都被删除了。
任何,我的意思是绝对任何,我能抓住的稻草真的很感激。
基于接受的答案,我改变了我的代码来处理来自其他线程的回调:我在#define
函数的开头引入了一个线程检查:
void doSomeWorkWithData()
一些相关的想法:
我正在考虑在void doSomeWorkWithData(A a)
{
if( QThread::currentThread() != this->thread() )
{
QMetaObject::invokeMethod( this,"doSomeWorkWithData"
,Qt::QueuedConnection
,Q_ARG(A, a) );
return;
}
/* The actual work this function does would be below here... */
};
语句之前引入QMutexLocker
,但决定反对它:可能并行使用的函数的唯一部分({{1以上的任何部分)在[{1}}语句中)是 - 据我所知 - 线程安全。
手动将连接类型设置为if
:技术上,如果我正确理解documentation,Qt应该做正确的事情和默认值{{ 1}},应该最终成为return;
。但是,当达到该陈述时总是如此,我决定明确地在那里提醒自己为什么会这样。
将排队代码直接放在函数中而不是将其隐藏在临时函数中:我本可以选择将if
的调用放在另一个临时函数中,比如Qt::QueuedConnection
invokeMethod { {1}}的Qt ::自动连接&#39;在Qt::AutoConnection
。我决定反对这一点,因为我似乎无法通过模板自动编码这个临时功能(模板和Meta系统是原始问题的一部分),所以&#34;用户&#34;我的代码(即实现Qt::QueuedConnection
的人)也必须手工输入临时函数(因为这就是模板化类型名称的正确解析方式)。包括对实际函数的检查在我看来是安全地输入一个额外的函数头,保持invokeMethod
界面更清洁一点,并且更好地提醒读者queueDoSomeWorkWithData()', which would be called by the callback in the listener and then uses
可能存在潜伏的线程问题暗。
答案 0 :(得分:4)
如果您确定单个函数将仅执行线程安全操作,则可以从另一个线程调用QObject的子类上的公共函数。
关于Qt的一个好处是它将处理外部线程以及它处理QThreads。因此,一种选择是为每个threadSafeDoSomeWorkWithData
创建一个doSomeWorkWithData
函数,除了QMetaMethod::invoke
非线程安全函数之外什么都不做。
public:
void threadSafeDoSomeWorkWithData(A a) {
QMetaMethod::invoke("doSomeWorkWithData", Q_ARG(A,a));
}
Q_INVOKABLE void doSomeWorkWithData(A a);
另外,Sergey Tachenov提出了一种在his answer here中或多或少做同样事情的有趣方式。他将我建议的两个功能合二为一。
void Obj2::ping() {
if (QThread::currentThread() != this->thread()) {
// not sure how efficient it is
QMetaObject::invoke(this, "ping", Qt::QueuedConnection);
return;
}
// thread unsafe code goes here
}
为什么在不创建GUI时会看到正常的行为?除了操纵GUI对象之外,您可能没有做任何其他不安全的事情。或者,也许它们是您的线程安全问题显而易见的唯一地方。