首页 > 其他分享 >Qt 子线程调用connect/QMetaObject::invokeMethod 不调用槽函数问题

Qt 子线程调用connect/QMetaObject::invokeMethod 不调用槽函数问题

时间:2024-06-09 13:43:40浏览次数:17  
标签:调用 Qt 线程 invokeMethod QMetaObject printCurrentThrad QDemoObject qdemoA

在使用invokeMethod 进行跨线程调用的时候,发现invokeMethod在某些情况下不能正常调用.

经过查各种资料发现invokeMethod底层的调用逻辑是通过Qt事件循环处理,所以子线程需要显示的调用QEventLoop::exec()或者QCoreApplication::processEvents()执行信号槽处理.

首先有一个QDemoObject类:

class QDemoObject : public QObject
{
    Q_OBJECT
public:
    explicit QDemoObject(QObject *parent = nullptr);
public slots:
    void printCurrentThrad();
signals:
    void sigPrintCurrentThrad();
};
QDemoObject::QDemoObject(QObject *parent)
    : QObject{parent}
{

}

void QDemoObject::printCurrentThrad()
{
    qDebug() << QThread::currentThreadId();
}

问题举例

例1

这里创建了两个线程:

第一个线程主要实例化QDemoObject。

假如QDemoObject中printCurrentThrad函数如果想要正常运行,需要保证printCurrentThrad函数一定要在QDemoObject实例化时(new QDemoObject)所在线程执行,也就是printCurrentThrad下的逻辑必须保证在线程1下执行。

为了在线程2中可以正常调用printCurrentThrad, 在第二个线程中尝试用invokeMethod异步方式(Qt::QueuedConnection)调用QDemoObject的printCurrentThrad方法:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        bool res = QMetaObject::invokeMethod(qdemoA, "printCurrentThrad", Qt::QueuedConnection);
     if(res) qDebug() << "call ok."; });

运行发现invokeMethod返回值res 为true(打印了call ok.),但是却没调用到printCurrentThrad函数!

例2

当尝试把调用invokeMethod异步方式改为同步方式即BlockingQueuedConnection:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        bool res = QMetaObject::invokeMethod(qdemoA, "printCurrentThrad", Qt::BlockingQueuedConnection);
        if(res) qDebug() << "call ok.";
    });

运行发现线程2卡在invokeMethod中,永远没机会向下执行!

例3

当尝试把调用invokeMethod改为立即执行(DirectConnection)时,正常调用printCurrentThrad函数:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        bool res = QMetaObject::invokeMethod(qdemoA, "printCurrentThrad", Qt::DirectConnection);
        if(res) qDebug() << "call ok.";
    });

虽然正常调用printCurrentThrad函数,但是printCurrentThrad的逻辑最终是在线程2中处理的,而不是线程1。

例4

当尝试把线程1的实例化移出到UI线程时,正常调用printCurrentThrad:

   //主线程(UI线程) 
  QDemoObject* qdemoA = nullptr; qdemoA = new QDemoObject; QtConcurrent::run([qdemoA]() {//线程2 bool res = QMetaObject::invokeMethod(qdemoA, "printCurrentThrad", Qt::QueuedConnection); if(res) qDebug() << "call ok."; });

 

正常调用printCurrentThrad,并且线程ID和GUI线程ID一致。

 例5

发现在子线程中使用connect绑定信号槽时也有同样的问题:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
        connect(qdemoA, &QDemoObject::sigPrintCurrentThrad, qdemoA, &QDemoObject::printCurrentThrad, Qt::QueuedConnection);
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        while(qdemoA == nullptr) QThread::msleep(100);
        emit qdemoA->sigPrintCurrentThrad();
    });

 线程2发送信号后,printCurrentThrad并没有在线程1中执行.

问题解决

例4可以正常调用主要是因为在主线程中的某处,一直在反复调用QCoreApplication::processEvents(),Qt程序在main方法中,执行到最后往往调用了exec()函数:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();
    return a.exec();
}

exec()内部可以理解成一个无限死循环,反复调用:

while(true) {
    QCoreApplication::processEvents();
}

 例1、例2不能正常调用printCurrentThrad是因为线程1中没有调用QCoreApplication::processEvents();,在线程1中调用QCoreApplication::processEvents();后,调用正常:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
        while(true) {
            //其他逻辑...
            QCoreApplication::processEvents();
            //其他逻辑...
        }
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        bool res = QMetaObject::invokeMethod(qdemoA, "printCurrentThrad", Qt::QueuedConnection);
        if(res) qDebug() << "call ok.";
    });

在线程2通过invokeMethod调用printCurrentThrad,会发现printCurrentThrad实际执行线程是线程1.

如果线程1中没有while主循环逻辑,也可以通过QEventLoop 的exec执行事件循环:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
        qDebug() << "thread1:" << QThread::currentThreadId();
        QEventLoop loop;
        loop.exec();
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        bool res = QMetaObject::invokeMethod(qdemoA, "printCurrentThrad", Qt::QueuedConnection);
        if(res) qDebug() << "call ok.";
    });

 

在例5中也是同样的道理:

    QDemoObject* qdemoA = nullptr;
    QtConcurrent::run([&qdemoA]() {//线程1
        qdemoA = new QDemoObject;
        connect(qdemoA, &QDemoObject::sigPrintCurrentThrad, qdemoA, &QDemoObject::printCurrentThrad, Qt::QueuedConnection);
        QEventLoop loop;
        loop.exec();
    });
    QtConcurrent::run([&qdemoA]() {//线程2
        while(qdemoA == nullptr) QThread::msleep(100);
        emit qdemoA->sigPrintCurrentThrad();
    });

 

标签:调用,Qt,线程,invokeMethod,QMetaObject,printCurrentThrad,QDemoObject,qdemoA
From: https://www.cnblogs.com/GengMingYan/p/18239440

相关文章

  • mqtt-emqx:设置遗嘱消息
    【pom.xml】<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.3.12.RELEASE</version></dependency><dependency><grou......
  • Qt基于SQLite数据库的增删查改demo
    一、效果展示在Qt创建如图UI界面,主要包括“查询”、“添加”、“删除”、“更新”,四个功能模块。查询:从数据库中查找所有数据的所有内容,并显示在左边的QListWidget控件上。添加:在右边的QLineEdit标签上输入需要添加的内容,包含id,name,age三个字段,然后点击添加按钮,即可将数据......
  • Electron qt开发教程
    模块安装打包 npminstall-gelectron-forgeelectron-forgeinitmy-project--template=vuenpmstart //进入目录启动//打包成一个目录到out目录下,注意这种打包一般用于调试,并不是用于分发npmrunpackage//打出真正的分发包,放在out\make目录下npmrunmakenpx@......
  • VNC打不开Qt Creator解决
    问题:设备机:debian、tightvncserver客户机:Windows、TigerVNCViewer远程连接VNC时打不开QtCreator。找了很多方法没能成功,例如-noload之类的命令。原因可能是虚拟桌面图形驱动问题。Qt论坛同样问题:https://forum.qt.io/topic/79580/opening-qt-from-remote-desktop-connectio......
  • PyQT5之多窗口交互
    方法一:不适用信号与槽MultiWin.py文件"""多窗口交互--不适用信号与槽"""importsysfromPyQt5.QtCoreimport*fromPyQt5.QtGuiimport*fromPyQt5.QtWidgetsimport*fromDateDialogimportDateDialogclassMultiWindow1(QWidget):def__init__......
  • PyQT5之为槽函数传递参数
    方法一:lambda表达式传递参数fromPyQt5.QtCoreimport*fromPyQt5importQtCorefromPyQt5.QtWidgetsimport*importsysclassLambdaSlotArg(QMainWindow):def__init__(self):super().__init__()self.setWindowTitle("使用Lambda表达式为槽函......
  • PyQT5信号与槽的连接
    方法一:fromPyQt5.QtCoreimport*fromPyQt5.QtWidgetsimport*importsysclassAutoSignalSlot(QWidget):def__init__(self):super().__init__()self.setWindowTitle("信号与槽自动连接")self.resize(300,100)self.okB......
  • PyQT5信号刷新时间
    importtimefromPyQt5.QtCoreimport*fromPyQt5.QtWidgetsimport*importsysclassBackendThread(QThread):update_date=pyqtSignal(str)defrun(self):whileTrue:data=QDateTime.currentDateTime()currentTim......
  • 通过site 包加载egg 或者whl pcakge 包并动态调用模块方法
    以前简单说过通过sys.path进行egg文件模块的加载,实际上我们可以结合site以及.pth能力,实现灵活的加载处理,同时通过importlib进行动态加载,以下是一个简单说明加载配置通过site包,添加自定义目录,目录里边包含.pth配置目录结构.pth内容使用核心是通过site添加......
  • PyQT5之信号关闭窗口
    fromPyQt5.QtCoreimport*fromPyQt5.QtWidgetsimport*importsysclassWinSignal(QWidget):#定义一个信号button_clicked_signal=pyqtSignal()def__init__(self):super().__init__()self.setWindowTitle("为窗口类添加信号")......