QThread目前有两种使用方法
1、继承QThread类,重写run方法
2、使用movetoThread
接下来大概讲讲其使用。
- 继承QThread方式
这种方式比较原始,也相对来说比较简单,继承后重写一下run函数,再启动线程就好了,废话不多说了,看下实例代码:
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject *parent = nullptr);
signals:
void runFinish();
protected:
void run() override{
static int times = 10;
while(times--)
{
qDebug() << "mythread run: id = " << QThread::currentThreadId();
QThread::msleep(500);
}
emit runFinish();
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void handleResult(){
qDebug() << "main thread: id = " << QThread::currentThreadId() << "thread run finish";
}
private:
Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
myThread *thread = new myThread(this);
connect(thread, &myThread::runFinish, this, &MainWindow::handleResult);
connect(thread, &myThread::finished, thread, &QObject::deleteLater);
thread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
运行看一下结果
这样一个线程就创建完毕了,我们可以在run函数里面实现我们想要的操作。我们如果想暂停或者继续的话一般会采用标志位的方式在while里面去进行处理,如下:
class myThread : public QThread
{
Q_OBJECT
public:
explicit myThread(QObject *parent = nullptr);
public slots:
void runstart(){
m_stop = false;
qDebug() << "start thread id = " << QThread::currentThreadId();
}
void runstop(){
m_stop = true;
qDebug() << "stop thread id = " << QThread::currentThreadId();
}
signals:
void runFinish();
protected:
void run() override{
int times = 10;
while(true)
{
while((!m_stop) && times)
{
times--;
qDebug() << "mythread run: id = " << QThread::currentThreadId();
QThread::msleep(500);
}
if(!times)
{
emit runFinish();
break;
}
}
}
private:
bool m_stop = true;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void handleResult(){
qDebug() << "main thread: id = " << QThread::currentThreadId() << "thread run finish";
}
private:
Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() << "\nmain thread: id = " << QThread::currentThreadId();
myThread *thread = new myThread(this);
connect(thread, &myThread::runFinish, this, &MainWindow::handleResult);
connect(thread, &myThread::finished, thread, &QObject::deleteLater);
connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstart);
connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstop);
thread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
运行并查看输出信息:
可以看到我们点击start跟stop按钮可以控制run里面函数是否执行。这个时候我们观察线程id号可以看到,在我们触发信号后,执行槽函数的线程是我们的主线程,也就是我们发射信号的那个线程。说到这有些小伙伴已经发现了一个问题,那就在槽函数中我们如果与run函数中访问同一数据时容易发生冲突,这里需要一个锁来进行同步。那有没有好的办法不需要使用锁去处理呢?使用movetoThread能够解决这一问题。
- 使用movetoThread方式
修改代码如下:
class Myworker : public QObject
{
Q_OBJECT
public:
explicit Myworker(QObject *parent = nullptr){}
public:
void setThreadStatus(bool status){
qDebug() << "slot thread id: " << QObject::thread()->currentThreadId();
}
public slots:
void doWork(){
int times = 10;
qDebug() << "worker thread id: " << QObject::thread()->currentThreadId();
while(times--)
{
static int count = 0;
qDebug() << "worker is working" << " " << count++;
QThread::msleep(200);
}
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void handleResult(){
qDebug() << "main thread: id = " << QThread::currentThreadId() << "thread run finish";
}
private:
Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() << "\nmain thread: id = " << QThread::currentThreadId();
Myworker *worker = new Myworker;
QThread *thread = new QThread(this);
worker->moveToThread(thread);
QObject::connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), worker, &Myworker::doWork);
QObject::connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), worker, [=](){
worker->setThreadStatus(true);
});
thread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
运行结果如下:
可以看到主线程id和执行槽函数线程id不一致,槽函数是由子线程执行的。这样主线程有时候可以通过信号槽的方式让子线程去执行比较耗时的操作,就不会造成主线程的卡顿了。其中connect的第五个参数Qt::ConnectionType会对结果造成影响,在QT信号与槽一文最后部分有对应参数的描述。单纯的文字描述也难以理解,借助QThread来说说我对这几个参数,Qt::AutoConnection,Qt::DirectConnection,Qt::QueuedConnection的理解。
我们在使用connect连接信号与槽时默认的参数就是这个,在第一种方式中创建的myThread对象依附于主线程,因此实际上接受者和发送者同在一个线程内,故实际默认使用的是Qt::AutoConnection这个参数。我们可以将
connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstart);
connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstop);
这部分改为
connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstart, Qt::DirectConnection);
connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), thread, &myThread::runstop, Qt::DirectConnection);
进行测试,实际上效果是一致的。将其替换为Qt::QueuedConnection后效果还是一样,因为他们在同一线程。我们再来看看Qt::DirectConnection描述:
也就是说槽函数一定是在信号发送那个线程执行。我们可以更改movetoThread方式创建的线程去测试,将连接信号槽部分进行修改如下:
QObject::connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), worker, &Myworker::doWork, Qt::DirectConnection);
QObject::connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), worker, [=](){
worker->setThreadStatus(true);
}, Qt::DirectConnection);
运行如下:
可以看到不管是槽函数还是那个worker线程都是主线程执行的,因为采用的是Qt::DirectConnection的模式,我们可以再修改测试一下,第一个connect不设置第五个参数(默认是Qt::AutoConnection)如下:
QObject::connect(ui->pushButton_start, QOverload<bool>::of(&QPushButton::clicked), worker, &Myworker::doWork);
QObject::connect(ui->pushButton_stop, QOverload<bool>::of(&QPushButton::clicked), worker, [=](){
worker->setThreadStatus(true);
}, Qt::DirectConnection);
运行结果如下
可以看到在触发stop信号后主线程立马进入执行了槽函数,因此也验证了我们的想法。上述方式实际pushButton_start按下发送信号后采用Qt::QueuedConnection方式进行连接,其描述如下
从运行的结果中也可以看出,当控制权回到接受者所依附线程的事件循环时,槽函数被调用。通俗点就是执行完dowork函数才会执行对应槽函数。
与主线程通讯
子线程与主线程通讯的一个解决方案是借助信号-槽的连接。这里将看到的一种使用信号-槽方式主次线程通讯并且处理耗时操作不导致主线程卡顿的方法记录一下。代码如下:
#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>
#include <QDebug>
class baseTask
{
public:
virtual int doWork(const int value) = 0;
virtual QString message() = 0;
};
class addNumber : public baseTask
{
int doWork(const int value){
QThread::msleep(5000);
return 10 + value;
}
QString message(){
return QString("Add Number");
}
};
class subNumber : public baseTask
{
int doWork(const int value){
QThread::msleep(5000);
return value - 10;
}
QString message(){
return QString("sub Number");
}
};
class mulNumber : public baseTask
{
int doWork(const int value){
QThread::msleep(5000);
return value * 10;
}
QString message(){
return QString("mul Number");
}
};
class taskThread : public QThread
{
Q_OBJECT
public:
taskThread(){}
~taskThread()
{
{
QMutexLocker locker(&mutex);
while(!tasks.isEmpty()){
delete tasks.dequeue();
}
tasks.enqueue(nullptr);
taskWait.wakeOne();
}
wait();
}
public:
void addTask(baseTask *task){
QMutexLocker locker(&mutex);
tasks.enqueue(task);
taskWait.wakeOne();
}
void setValue(int value){
QMutexLocker locker(&mutex);
currentValue = value;
}
int getValue(){
QMutexLocker locker(&mutex);
return result;
}
protected:
void run() override
{
baseTask *task = nullptr;
int value;
while(1)
{
{
QMutexLocker locker(&mutex);
if(tasks.isEmpty())
taskWait.wait(&mutex);
qDebug() << "$$$$$$$$$$$$$$$$$$";
task = tasks.dequeue();
if(!task) break;//说明需要退出线程
value = currentValue;
}
emit taskStarted(task->message());//发送正在处理的信息
qDebug() << currentValue;
result = task->doWork(value);
delete task;
if(tasks.isEmpty())
emit tasksFinish();
}
}
signals:
void taskStarted(const QString &message);
void tasksFinish();
private:
int result;
int currentValue;
QQueue<baseTask *> tasks;
QWaitCondition taskWait;
QMutex mutex;
};
class myWidget : public QWidget
{
Q_OBJECT
public:
explicit myWidget(QWidget *parent = 0) : QWidget(parent)
{
thread.start();
thread.setValue(20);
label = new QLabel("ready", this);
label->move(100, 100);
label->resize(200, 200);
add = new QPushButton("add", this);
sub = new QPushButton("sub", this);
mul = new QPushButton("mul", this);
test = new QPushButton("test", this);
add->move(0, 0);
sub->move(100, 0);
mul->move(200, 0);
test->move(300, 0);
connect(add, SIGNAL(clicked(bool)), this, SLOT(on_add_clicked(bool)));
connect(sub, SIGNAL(clicked(bool)), this, SLOT(on_sub_clicked(bool)));
connect(mul, SIGNAL(clicked(bool)), this, SLOT(on_mul_clicked(bool)));
connect(&thread, SIGNAL(taskStarted(QString)), this, SLOT(showMessage(QString)));
connect(&thread, SIGNAL(tasksFinish()), this, SLOT(doFinish()));
}
~myWidget(){}
private:
void addTask(baseTask *task){
thread.addTask(task);
add->setEnabled(false);
sub->setEnabled(false);
mul->setEnabled(false);
}
private slots:
void showMessage(QString message){
label->setText(message);
}
void doFinish(){
label->setText(QString("result = %1").arg(thread.getValue()));
add->setEnabled(true);
sub->setEnabled(true);
mul->setEnabled(true);
}
void on_add_clicked(bool){
addTask(new addNumber);
}
void on_sub_clicked(bool){
addTask(new subNumber);
}
void on_mul_clicked(bool){
addTask(new mulNumber);
}
private:
taskThread thread;
QLabel *label;
QPushButton *add;
QPushButton *sub;
QPushButton *mul;
QPushButton *test;
};
这里我们假设add、sub、mul三个都是比较耗时的操作,由于具有类似的特性,我们定义一个标准的基类baseTask,通过继承的方式让它们实例化。由于两个线程都存在对变量操作的情况,因此在taskThread中对相关的变量访问都需要加锁进行处理。程序运行后我们点击对应的add、sub、mul操作都是转到子线程中进行操作的,因此不会存在卡界面的问题。