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

Multithreading 如何在从非Qt多线程库调用的QObject派生类中实现回调函数? (伪)码,multithreading,qt,callback,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 *

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

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

};
一些相关的想法:

  • 我曾考虑在
    if
    语句之前引入
    QMutexLocker
    ,但我决定不这样做:函数中唯一可能并行使用的部分(在
    if
    语句中
    return;
    以上的部分)是线程安全的

  • 手动将连接类型设置为
    Qt::QueuedConnection
    :从技术上讲,如果我理解正确,Qt应该做正确的事情,默认的
    Qt::AutoConnection
    ,应该最终成为
    Qt::QueuedConnection
    。但是,由于当这个声明达成时,情况总是如此,我决定明确地把它放在那里,以提醒自己为什么会有这样的情况

  • 将排队代码直接放在函数中,而不是隐藏在临时函数中:我可以选择将对
    invokeMethod
    的调用放在另一个临时函数中,比如
    queueDoSomeWorkWithData(),它将由侦听器中的回调调用,然后使用
    invokeMethod
    doSomeWorkWithData()
    上的
    Qt::AutoConnection'。我决定不这样做,因为我似乎没有办法通过模板自动编码这个临时函数(模板和元系统是原始问题的一部分),所以我的代码的“用户”(即实现
    doSomeWorkWithData(XYZ XYZ)
    )也必须手动键入临时函数(这就是模板化类型名称正确解析的方式)。在我看来,将签入实际函数似乎可以安全地键入额外的函数头,使
    MyClass
    界面更干净,并更好地提醒读者
    doSomeWorkWithData()
    可能有一个线程问题潜伏在黑暗中


如果您确定单个函数只执行线程安全操作,那么可以从另一个线程调用QObject子类上的公共函数

Qt的一个优点是,它处理外部线程的能力和处理QThreads的能力一样好。因此,一个选项是为每个
doSomeWorkWithData
函数创建一个
threadSafeDoSomeWorkWithData
函数,该函数只执行
QMetaMethod::invoke
非线程安全函数

public:
  void threadSafeDoSomeWorkWithData(A a) { 
    QMetaMethod::invoke("doSomeWorkWithData", Q_ARG(A,a)); 
  } 
  Q_INVOKABLE void doSomeWorkWithData(A a);
或者,Sergey Tachenov提出了一种有趣的方法,可以在中做或多或少相同的事情。他将我建议的两个功能合并为一个

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

你能否澄清标记为
public Q_SIGNALS:
的部分是否应该是
public:
QSIGNALS:
?是
doSomeWorkWithData(A)
直接操纵QGraphicsItems,还是其他对象捕捉到的信号起作用?@Slavik81:嗯,实际上应该是
public Q\u QSLOTS:
…我编辑了这个示例。抱歉混淆。Wrt.图形项目:
doSomeWorkWithData(A)
影响图形项目,因为
a
包含有关(自定义)项目的形状和位置的信息。
doSomeWorkWithData(a)
使用项目
setPos()
方法更新位置,并使用其他方法更改形状。(项目内部应该做正确的事情,即使用
prepareGeometryChange()
,等等)谢谢你的这个想法和链接。我目前正在尝试通过检查
QThread::currentThread()!=this->thread()
(我没有意识到我可以这么做)和通过
QMetObject::invokeMethod()排队等待调用来解决问题。我将根据结果更新我的问题(也就是说,我也将推迟接受答复)。
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
}