如何在从非Qt多线程库调用的QObject派生类中实现回调函数?

时间:2013-03-06 01:08:07

标签: multithreading qt callback

(伪)代码

以下是我遇到问题的概念的不可编辑的代码草图:

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();

};

实际代码中的更多细节:

示例中的ListenerRTI DataReaderListener,即回调 函数是onDataAvailable()

我想要完成的事情

我正在尝试编写一个使用RTI's Connext DDS进行通信的小型分布式程序,并使用Qt5编写GUI内容 - 但是,我不相信这些详细信息对问题很重要据我所知,归结为以下几点:

  • 我有一个QObject派生的对象myObject,其线程关联性可能与主GUI线程相关,也可能不同(但为了简单起见,我们假设是这种情况。)
  • 我希望该对象能够对发生在另一个非Qt第三方库中的事件做出反应(在上面的示例代码中由函数doSomeWorkWithData()表示。

到目前为止我理解为什么会出现问题

免责声明:像往常一样,在开始新项目时,总会有不止一件新事物学到。对我来说,这里的新东西是RTI的Connext和(显然)我自己第一次处理线程。

从阅读有关Qt中的线程(12345),我认为

  • QObjects一般不是线程安全的,即我必须对事情有点小心
  • 使用&#34; 沟通的正确方式&#34;使用QObjects应该允许我避免不得不自己处理互斥锁等,即其他人(Qt?)可以负责为我序列化访问。

因此,我不能({随机)调用MyClass::doSomeWorkWithData()但我需要序列化它。一个(可能很简单)这样做的方法是将事件发布到事件队列myObject,其中 - 当时间可用时 - 将触发执行所需方法MyClass::doSomeWorkWithData()情况下。

我试图让事情发挥作用

我已经确认myObject与上面的示例代码类似地实例化时,与主GUI线程相关联,即myObject.thread() == QApplication::instance()->thread()

有了这个,我到目前为止尝试了三个选项:

选项1:直接调用函数

这种方法基于这样的事实 - myObject存在于GUI线程中 - 所有创建的侦听器也按原样隶属于GUI线程 由`myObject&#39;创建并以这种方式继承其线程

这实际上导致执行doSomeWorkWithData()的事实。然而, 其中一些函数操纵QGraphicsItems,只要我得到这种情况 错误消息读取:&#34; QObject :: startTimer:无法从另一个启动计时器 螺纹&#34;

选项2:通过QMetaObject::invokeMethod()

发布活动

试图通过正确发布myObject的事件来规避此问题,我 我试图用Q_INVOKABLE标记MyObject::doSomeWorkWithData(),但我没有调用它 因为我需要使用Q_ARG传递参数。我正确注册并声明了我的自定义类型 在示例中由struct A等表示,但我在事实上失败了 Q_ARG扩展为包含一个类型的参数的文字,在...中 模板化的案例不起作用("T"不是注册或声明的类型)。

尝试使用传统信号和插槽

这种方法基本上直接失败,因为QMeta系统没有 使用模板,即在我看来,根本不可能是任何模板化的QObject。

我想帮助

花了大约一个星期的时间尝试解决这个问题,阅读线程(并在我的代码中发现一些其他问题),我真的很想完成正确的。 因此,我真的很感激,如果:

  1. 有人可以向我展示如何通过来自另一个第三方库(或其他任何其他内容)的回调函数从不同的非QThread调用QObject的成员函数的通用方法控制,线程。

  2. 有人可以向我解释为什么选项1可以工作,如果我只是不创建一个GUI,即完成所有相同的工作,只是没有QGraphcisScene可视化它(以及项目&#39; s {{ 1}}成为app而不是QCoreApplication,并且所有与图片相关的作品QApplication都被删除了。

  3. 任何,我的意思是绝对任何,我能抓住的稻草真的很感激。

    更新

    基于接受的答案,我改变了我的代码来处理来自其他线程的回调:我在#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可能存在潜伏的线程问题暗。

1 个答案:

答案 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对象之外,您可能没有做任何其他不安全的事情。或者,也许它们是您的线程安全问题显而易见的唯一地方。