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信号槽机制源码解析







posted on 2021-05-28 15:56  林西索  阅读(2451)  评论(0编辑  收藏  举报