- 0. QT中线程启动的方式
- 0.1 继承QThread
- 0.2 使用moveToThread启动
- 0.3 使用QtConcurrent启动
- 1. 对象方法调用时的坑
- 1.1 对象方法调用方式
- 1.2 坑
- 2. 信号槽的5种连接方式
- 2.1 直接连接
- 2.2 队列连接
- 2.3 阻塞队列连接
- 2.4 自动连接
- 2.5 单一连接
- 3. 测试代码
继承QThread时,子类必须重写run方法,保证线程在手动结束之前持续运行。
当子类使用start方法启动后,run方法会在生命周期内循环执行。
.h文件
#ifndef CLASS1_H #define CLASS1_H #include#include // 继承QTQThread class Class1 : public QThread { Q_OBJECT public: explicit Class1(QThread *parent = 0); ~Class1(); void run() override; public slots: void testFunc(); }; #endif // CLASS1_H
.cpp文件
#include "class1.h" #include0.2 使用moveToThread启动Class1::Class1(QThread *parent) : QThread(parent) { } Class1::~Class1() { } void Class1::run() { qDebug() << "class 1 run threadID : " << currentThreadId(); while(1){ // 测试代码,写成了死循环,实际中应该避免这样写 msleep(5); } } void Class1::testFunc() { qDebug() << "class1 testFunc threadID : " << currentThreadId(); }
这种方式较为简单,正常写自己的类即可,为了保证其方法可以在线程上执行,需要继承QObject类。
在使用时首先生成子类对象,同时new一个QThread出来,然后使用子类的moveToThread方法,将子类放到刚刚new出来的QThread上,再启动该线程即可。
.h文件
#ifndef CLASS2_H #define CLASS2_H #include// 使用moveToThread class Class2 : public QObject { Q_OBJECT public: explicit Class2(QObject *parent = nullptr); ~Class2(); public slots: void testFunc(); }; #endif // CLASS2_H
.cpp文件
#include "class2.h" #include0.3 使用QtConcurrent启动#include Class2::Class2(QObject *parent) : QObject(parent) { } Class2::~Class2() { } void Class2::testFunc() { qDebug() << "class2 testFunc threadID : " << QThread::currentThreadId(); }
QtConcurrent方式通常用于在当前线程中启动一个耗时的方法,将该方法单独放在一个线程上执行。只需要一行代码即可。
启动后,当前线程不会阻塞,继续向下执行。可以配合QFuture类获取异步执行的结果。
有两种。
- 直接调用,object.func()或者object->func
- 使用QT中的信号槽方式,
使用继承QThread的方式时,只有run方法和run调用的方法会在子线程上执行。
- 当主线程直接调用该类的某个方法时,该方法由主线程负责执行。
- 当主线程使用信号槽的方式调用该类的某个方法时,该方法由主线程负责执行。
使用moveToThread的方式时,具有更好的灵活性。
- 当主线程直接调用该类的某个方法时,该方法由主线程负责执行。
- 当主线程使用信号槽的方式调用该类的某个方法时,由主线程执行还是由子线程执行,根据信号槽的连接方式确定。
在Qt的信号槽机制当中,有5种连接方式。
在代码中使用信号槽机制的时候,通常只需要下边的一行代码
connect(sender, signal, receiver, slot)
connect方法除了信号发送者、信号、信号接收者、槽函数之外,还有第五个参数:这对信号槽的连接方式。
Qt::DirectConnection
槽函数在信号发送者所在的线程中执行,效果就像是在信号发射位置直接调用槽函数。
Qt::QueuedConnection
槽函数在信号接收者所在线程中执行,但是,该槽函数不会立即被执行,是放入到信号接收者线程中的槽函数队列内,等到该槽函数前边的任务都执行完毕,才会执行该槽函数。
可见,如果槽函数接收者线程中有死循环槽函数被执行的话,那么在死循环之后的槽函数就永远都不会被执行。
使用这种方式时,信号发送者信号发出后,当前线程不会阻塞,会继续向下执行。
2.3 阻塞队列连接Qt::BlockingQueuedConnection
与队列连接类似,但:在使用阻塞队列连接方式,发送信号后,当前线程会进入到阻塞状态,直到槽函数执行完毕才恢复。
可见:使用这种方式时,如果信号和槽函数在同一个线程中,会造成死锁。
2.4 自动连接一般情况下,或者刚接触Qt开发时,都是使用的这种方式进行连接。
在自动连接方式中:
- 如果信号发送者和接收者在同一个线程中,则使用直接连接Qt::QueuedConnection
- 如果信号发送者和接收者在不同线程中,则使用队列连接Qt::QueuedConnection
Qt::UniqueConnection
其实,这个不算连接方式,该值可以确保当前信号和当前槽函数的连接仅进行一次,不可以进行重复的相同连接。
可以通过或运算,与其他4种方式结合使用。
创建Qt工程,创建界面如下:
在ui类中添加class1和class2两个类的指针(上边有代码),class1使用继承QThread的方式,class2使用moveToThread的方式。
界面中的4个按钮都是调用class1和class2对象中的testFunc方法输出对象方法的执行线程ID。但调用方式不同。
TestClass1和TestClass2按钮直接调用,其余两个使用信号槽的方式调用。
代码如下:
.h 文件
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include#include #include #include #include "class1.h" #include "class2.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); signals: void signal_test1_func(); void signal_test2_func(); private slots: void on_pushButton_test1_clicked(); void on_pushButton_test1s_clicked(); void on_pushButton_test2_clicked(); void on_pushButton_test2s_clicked(); private: Ui::MainWindow *ui; Class1 *m_c1Thread = nullptr; // 继承QThread的 Class2 *m_c2 = nullptr; // 使用moveToThread QThread *m_c2Thread = nullptr; }; #endif // MAINWINDOW_H
.cpp 文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() << "main threadID : " << QThread::currentThreadId();
m_c1Thread = new Class1;
m_c2 = new Class2;
m_c2Thread = new QThread(this);
m_c2->moveToThread(m_c2Thread);
connect(this, &MainWindow::signal_test1_func, m_c1Thread, &Class1::testFunc);
connect(this, &MainWindow::signal_test2_func, m_c2, &Class2::testFunc); // 这里的连接方式一会要做修改
m_c1Thread->start();
m_c2Thread->start();
}
MainWindow::~MainWindow()
{
m_c1Thread->quit();
m_c1Thread->wait();
m_c2Thread->quit();
m_c2Thread->wait();
delete m_c1Thread;
delete m_c2Thread;
delete m_c2;
delete ui;
}
void MainWindow::on_pushButton_test1_clicked()
{
// m_c1Thread 继承自 QThread
m_c1Thread->testFunc();
}
void MainWindow::on_pushButton_test1s_clicked()
{
// m_c1Thread 继承自 QThread
emit signal_test1_func();
}
void MainWindow::on_pushButton_test2_clicked()
{
// m_c2 使用 moveToThread 启动
m_c2->testFunc();
}
void MainWindow::on_pushButton_test2s_clicked()
{
// m_c2 使用 moveToThread 启动
emit signal_test2_func();
}
注意代码中的
connect(this, &MainWindow::signal_test2_func, m_c2, &Class2::testFunc);
这里,class2对象的连接方式使用默认的方式,即自动连接。
程序运行后,按第一行、第二行的顺序点击4个按钮,输出结果如下:
仅有第四个按钮(使用moveToThread启动线程,信号槽方式调用对象方法)在子线程上执行。可见,class2对象的连接方式此时是队列连接。
当修改class2对象的连接方式为队列
connect(this, &MainWindow::signal_test2_func, m_c2, &Class2::testFunc, Qt::QueuedConnection);
再次执行,得到结果如下:
四个按钮的方法都在主线程上执行了,印证了1.2节中的描述。



