关于Qt使用moveToThread建立多线程信号与槽不触发的问题

  • Qt建立多线程的方法一般有两种
  1. 新建一个类MyThread,继承QThread类,重写run()函数,通过调用start()函数启动线程。
  2. 新建一个类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
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 }
mythread.cpp
 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
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 }
widget.cpp

一开始按照使用槽函数的形式传递信号,程序运行之后可以正常启动,但是按下暂停按钮后子线程不暂停。查找了诸多资料,发现跟事件循环机制有关。

原因:Qobject对象是有线程归属的,或者说其存活在特定线程中。当接收到队列连接的信或 投递的事件,槽函数或事件处理在其归属线程中执行。 如果一个对象没有归属线程(也就是thread()接口调用返回值为空),或者其所属线程没有运行中的消息循环,队列连接的信号或投递的事件是无法正常接收的。

原文链接:Qt跨线程信号槽槽函数无响应(未调用)问题

  • 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()函数开启)。
 
解决办法:
  1. 开启事件循环机制------目前不知如何使用代码开启事件循环机制,有的博主贴上去的代码,线程类同时继承QObject和QThread类,通过QThread类的exec()函数开启事件循环机制,实测由于多继承的问题会报错。。。
  2. connect连接方式改为直接连接,相当于在主线程中调用子线程的函数。
  3. 可以将connect中的槽函数在Lambda表达式中调用,跟方法二同理都是在主线程中调用子线程的函数。

 

posted @ 2024-10-15 15:48  不看春晚好多年  阅读(1615)  评论(0)    收藏  举报