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