关于Qt使用moveToThread建立多线程信号与槽不触发的问题
- Qt建立多线程的方法一般有两种
- 新建一个类MyThread,继承QThread类,重写run()函数,通过调用start()函数启动线程。
- 新建一个类MyThread,继承QObject类,新建类不可指定父对象!新建一个QThread的变量thread,新类的实例对象使用moveToThread(&thread)将自己移动到新线程去,调用start()函数启动线程,但此时子线程虽然启动了,但任务函数未启动,所以需要使用connect函数将start()信号与任务函数连接。
案例如下:

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QObject> 5 #include <QThread> 6 #include <QMutex> 7 #include <QWaitCondition> 8 #include <QMutexLocker> 9 #include <QDebug> 10 11 class MyThread : public QObject 12 { 13 Q_OBJECT 14 public: 15 explicit MyThread(QObject *parent = nullptr); 16 17 void work(); 18 void pause(); 19 void resume(); 20 21 private: 22 bool paused; 23 QMutex mtx; 24 QWaitCondition condition; 25 26 signals: 27 void sendNum(int num); 28 29 }; 30 31 #endif // MYTHREAD_H
1 #include "mythread.h" 2 3 MyThread::MyThread(QObject *parent) 4 : QObject{parent} 5 { 6 paused = false; 7 } 8 9 void MyThread::work() 10 { 11 int num = 0; 12 while(num<1000){ 13 mtx.lock(); 14 if(paused) 15 { 16 condition.wait(&mtx); 17 } 18 mtx.unlock(); 19 emit sendNum(num); 20 QThread::msleep(500); 21 num++; 22 // exec(); 23 } 24 } 25 26 void MyThread::pause() 27 { 28 qDebug()<<"接收休眠信号"; 29 qDebug()<<"线程ID:"<<QThread::currentThreadId(); 30 QMutexLocker<QMutex> locker(&mtx); 31 paused = true; 32 } 33 34 void MyThread::resume() 35 { 36 qDebug()<<"接收唤醒信号"; 37 qDebug()<<"线程ID:"<<QThread::currentThreadId(); 38 { 39 QMutexLocker<QMutex> locker(&mtx); 40 paused = false; 41 } 42 condition.notify_one(); 43 }
1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include <QThread> 6 #include <QDebug> 7 #include <QEventLoop> 8 9 #include "mythread.h" 10 11 QT_BEGIN_NAMESPACE 12 namespace Ui { 13 class Widget; 14 } 15 QT_END_NAMESPACE 16 17 class Widget : public QWidget 18 { 19 Q_OBJECT 20 public: 21 Widget(QWidget *parent = nullptr); 22 ~Widget(); 23 24 private: 25 Ui::Widget *ui; 26 27 QThread *thread; 28 MyThread *mythread; 29 30 bool isRun; 31 bool paused; 32 33 signals: 34 void sendSleep(); 35 void sendWake(); 36 37 private slots: 38 void on_lauch_clicked(); 39 void on_pause_clicked(); 40 }; 41 #endif // WIDGET_H
1 #include "widget.h" 2 #include "ui_widget.h" 3 4 Widget::Widget(QWidget *parent) 5 : QWidget(parent) 6 , ui(new Ui::Widget) 7 { 8 ui->setupUi(this); 9 10 paused = false; 11 isRun = false; 12 13 thread = new QThread(); 14 mythread = new MyThread(); 15 16 mythread->moveToThread(thread); 17 18 connect(thread,&QThread::started,mythread,&MyThread::work); 19 connect(mythread,&MyThread::sendNum,this,[&](int num){ 20 ui->lcdNumber->display(num); 21 }); 22 23 // connect(this,&Widget::sendSleep,mythread,&MyThread::pause); 24 // connect(this,&Widget::sendWake,mythread,&MyThread::resume); 25 26 connect(this,&Widget::sendSleep,this,[&](){ 27 mythread->pause(); 28 qDebug()<<QThread::currentThreadId(); 29 }); 30 connect(this,&Widget::sendWake,this,[&](){ 31 mythread->resume(); 32 qDebug()<<QThread::currentThreadId(); 33 }); 34 35 qDebug()<<thread->isRunning(); 36 } 37 38 Widget::~Widget() 39 { 40 thread->deleteLater(); 41 delete mythread; 42 delete ui; 43 } 44 45 void Widget::on_lauch_clicked() 46 { 47 if(thread->isRunning()&&paused == false){ 48 qDebug()<<"什么也不做"; 49 return; 50 } 51 else if(thread->isRunning() == false){ 52 qDebug()<<"线程启动"; 53 thread->start(); 54 } 55 else if(thread->isRunning()&&paused){ 56 qDebug()<<"发射唤醒信号"; 57 emit sendWake(); 58 // exec(); 59 // QEventLoop::exec(); 60 paused = false; 61 } 62 qDebug()<<thread->isRunning(); 63 } 64 65 66 void Widget::on_pause_clicked() 67 { 68 if(thread->isRunning()&&paused == false) 69 { 70 qDebug()<<"发射休眠信号"; 71 emit sendSleep(); 72 paused = true; 73 } 74 }
一开始按照使用槽函数的形式传递信号,程序运行之后可以正常启动,但是按下暂停按钮后子线程不暂停。查找了诸多资料,发现跟事件循环机制有关。
原因:Qobject对象是有线程归属的,或者说其存活在特定线程中。当接收到队列连接的信或 投递的事件,槽函数或事件处理在其归属线程中执行。 如果一个对象没有归属线程(也就是thread()接口调用返回值为空),或者其所属线程没有运行中的消息循环,队列连接的信号或投递的事件是无法正常接收的。
- connect的第五个参数为连接方式
Qt::AutoConnection 默认连接
Qt::DirectConnection 槽函数立即调用
Qt::BlockingQueuedConnection 同步调用
Qt::QueuedConnection 异步调用
Qt::UniqueConnection 单一连接
(1) Qt::DirectConnection(立即调用)
直接在发送信号的线程中调用槽函数(无论发送信号和槽函数是否位于同一线程),等价于槽函数的实时调用。
也就是说槽函数在发送信号所在线程调用。直接连接,其实就等同于直接调用。
(2) Qt::QueuedConnection(异步调用)
信号发送至目标线程的事件队列(发送信号和槽函数位于不同线程),交由目标线程处理,当前线程继续向下执行。
当信号发送时候,槽函数不会直接调用,直到接受者线程取得控制权时进行事件处理循环时候,槽函数才会调用执行。
跨线程时只有队列连接是安全的,队列连接借助的是事件系统,所以你可以通过postEvent在线程间传递数据。
(3) Qt::BlockingQueuedConnection(同步调用)
信号发送至目标线程的事件队列,由目标线程处理。当前线程阻塞等待槽函数的返回,之后向下执行。
Qt::BlockingQueuedConnection 和QueuedConnectionx相同,但是sender发送后线程会进入阻塞状态,
只有receiver线程执行槽函数完成,才会结束阻塞状态,所以这种参数类型设定情况下,sender和receiver不能在同一个线程,否则会造成死锁发生。
(4) Qt::AutoConnection(默认连接)
当发送信号线程=槽函数线程时,效果等价于Qt::DirectConnection;
当发送信号线程!=槽函数线程时,效果等价于Qt::QueuedConnection。
Qt::AutoConnection是connect()函数第5个参数的默认值,也是实际开发中字常用的连接方式。
(5) Qt::UniqueConnection(单一连接)
功能和AutoConnection相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。
这也就解释为什么子线程没有响应信号的原因了,使用moveToThread建立的线程,默认没有开启事件循环机制,所以子线程无法处理信号。使用继承QThread类重写run函数的方式建立的线程,在4.4版本及以后默认开启事件循环机制(之前版本可以使用exec()函数开启)。
解决办法:
- 开启事件循环机制------目前不知如何使用代码开启事件循环机制,有的博主贴上去的代码,线程类同时继承QObject和QThread类,通过QThread类的exec()函数开启事件循环机制,实测由于多继承的问题会报错。。。
- connect连接方式改为直接连接,相当于在主线程中调用子线程的函数。
- 可以将connect中的槽函数在Lambda表达式中调用,跟方法二同理都是在主线程中调用子线程的函数。

浙公网安备 33010602011771号