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

在使用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();
    });

 

posted @ 2024-06-09 13:41  耿明岩  阅读(18)  评论(0编辑  收藏  举报
希望能帮助到你,顺利解决问题! ...G(^_−)☆