Qt --- 信号槽:第五个参数
connect第五个参数说明:
1、Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
2、Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
3、Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
4、Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
5、Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
使用Qt::QueuedConnection需要注意:对于信号槽的参数类型,是有限制的,必须得是 meta-object type.
Qt文档中有指出:With queued connections, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes.
一。 遇到的问题:在GUI主线程向另一个线程发送信号,槽函数不执行。
1 static EventLoop* gEventLoop = nullptr; 2 3 void FFF() 4 { 5 gEventLoop = new EventLoop(); 6 } 7 8 MainWindow::MainWindow(QWidget *parent) : 9 QMainWindow(parent), 10 ui(new Ui::MainWindow) 11 { 12 ui->setupUi(this); 13 14 std::thread ttt(FFF); //另一个线程中创建对象 15 ttt.join(); 16 17 connect(this, &MainWindow::Call, gEventLoop, &EventLoop::Called, Qt::QueuedConnection); //连接成功 槽函数不执行 18 connect(gEventLoop, &EventLoop::eee, this, &MainWindow::HHH, Qt::QueuedConnection); 19 20 }
connect(this, &MainWindow::Call, eLoop, &EventLoop::Called, Qt::QueuedConnection); //连接成功 槽函数不执行
这里因为发送信号的对象和接收信号的对象不在同一个线程,使用QueueConnection,但是发送信号后 槽函数不会执行。
原因:
信号发送成功后,会把信号放到接收对象所在线程的事件循环队列,排到它的时候,自动调用槽函数。
但是std::thread创建的线程中,没有事件循环,所以没有接收到信号,槽函数不会执行。
解决方法:
1.为线程创建一个事件循环。(不会。。。)
2.使用QThread开启线程,QThread自带默认事件循环。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // std::thread ttt(FFF); //另一个线程中创建对象 // ttt.join(); // connect(this, &MainWindow::Call, eLoop, &EventLoop::Called, Qt::QueuedConnection); //连接成功 槽函数不执行 // connect(eLoop, &EventLoop::eee, this, &MainWindow::HHH, Qt::QueuedConnection); qThread = new QThread(); eLoop = new EventLoop(); eLoop->moveToThread(qThread); connect(qThread, &QThread::finished, qThread, &QObject::deleteLater); connect(qThread, &QThread::destroyed, [=](){qThread = nullptr;}); connect(qThread, &QThread::finished, eLoop, &QObject::deleteLater); connect(eLoop, &QObject::destroyed, [=](){eLoop = nullptr;}); connect(this, &MainWindow::Call, eLoop, &EventLoop::Called, Qt::QueuedConnection); connect(eLoop, &EventLoop::eee, this, &MainWindow::HHH, Qt::QueuedConnection); // QObject::connect(eLoop, &EventLoop::eee, this, &MainWindow::HHH, Qt::QueuedConnection); qThread->start(); } MainWindow::~MainWindow() { qThread->exit();//结束线程(结束事件循环) qThread->wait();//阻塞 等待线程结束 delete ui; }
说明:
qThread = new QThread();
eLoop = new EventLoop(); //创建对象
eLoop->moveToThread(qThread); //把对象放到线程中执行,此时线程自带事件循环,发送信号,槽函数能正确调用。(参考 https://www.huaweicloud.com/articles/2a9c964f7ee0900f9bd029224b9f2b26.html)
eLoop->moveToThread(qThread); //把对象放到线程中执行,此时线程自带事件循环,发送信号,槽函数能正确调用。
更正:moveToThread() 函数的作用是将槽函数在指定的线程中被调用(调用指的是:发送信号后,槽函数会被放到指定线程的事件循环队列中等待执行。)。如果直接调用槽函数,那就是普通的函数调用过程。
参考自:https://zhuanlan.zhihu.com/p/53270619
小结:
- 使用moveToThread这种QT多线程的方法,实现简单、使用灵活,并且思路清晰,相对继承于QThread类的方式更有可靠性,这种方法也是官方推荐的实现方法。如果线程要用到事件循环,使用继承QObject的多线程方法无疑是一个更好的选择;
- 创建QObject派生类对象不能带有父类;
- 把线程的finished信号和obj对象、QThread对象的 QObject::deleteLater 槽连接,这个信号槽必须连接,否则会内存泄漏;如果QObject的派生类和QThread类指针是需要重复使用,那么就需要处理由对象被销毁之前立即发出的 QObject::destroyed 信号,将两个指针设置为nullptr,避免出现野指针;
- 调用QThread::start是默认启动事件循环;
- 必须需要使用信号槽的方式使用线程;
- 需要注意跨线资源的创建,例如QTimer、QUdpSocket等资源,如果需要在子线程中使用,必须要在子线程创建;
- 要善用QObject::deleteLater 和 QObject::destroyed来进行内存管理 ;
- 尽量避免使用terminate强制退出线程,若需要退出线程,可以使用quit或exit;
追溯历史,在 Qt 4.4 版本以前的 QThread 类是个抽象类,要想编写多线程代码唯一的做法就是继承 QThread 类。但是之后的版本中,Qt 库完善了线程的亲和性以及信号槽机制,我们有了更为优雅的使用线程的方式,即 QObject::moveToThread()。这也是官方推荐的做法,遗憾的是网上大部分教程没有跟上技术的进步,依然采用 run() 这种腐朽的方式来编写多线程程序。-》https://zhuanlan.zhihu.com/p/53270619
深入理解-》 Qt信号槽机制源码解析