简介
线程就是运行程序的东西,比如电脑系统就需要一个线程去运行,但是如果想打开软件,就必须给他开一个线程让其运行,否则一个线程是无法运行两个程序的,就会出现卡死状态。
qt使用线程必须自定义类并继承使用。
qt线程有两种使用方式:
- 第一种:qt4.8版本之前所使用的,继承于QThread类,重写run方法进行实现;
- 第二种:qt4.8版本之后,一直大力推崇的线程使用方式,继承于QOBject类,使用moveToThread方式实现。
这篇博客介绍(记录)第二种方式是如何使用线程的。
线程
线程的使用方式前篇一律,这里写下项目中常用的方式。
当然线程的使用离不开信号与槽的机制,我们这里使用信号与槽可以得出一个三角恋的关系,如下图:
由此图可以得出三角关系可知:
- 主程序与线程进行信号与槽的绑定;
- 线程去执行方法;
- 方法类与主程序进行信号与槽的绑定;
- 线程也与主程序进行信号与槽的绑定。
1. 自定义类继承QObject
自定义的线程类中写下头文件#include <QObject>
当然线程的cpp文件需要包含执行方法的类的头文件。
继承如下:
class MyThread : public QObject {
Q_OBJECT
};
类中包含一个信号与一个槽函数:
public:
// 槽函数,此函数调用方法执行(参数必须和关联的信号的参数一致)
void triggerFunc(Test *test);
signals:
// 信号,函数执行完毕后发射信号告诉主程序执行完毕了
void trigger();
2. 创建一个新类用于执行需要的操作
这里我们创建一个Test类用于操作,包含一个test()方法用于执行真正的操作。
class Test : public QWidget {
public:
// 真正需要执行的操作
void test();
};
3. 创建主程序类
主程序需要包含头文件:
- 自定义线程类;
- 需要执行操作的类;
- 系统线程类。
#include <QThread>
class TestQThreadThree : public QWidget {
Q_OBJECT
signals:
// 发射信号触发线程执行,信号带参,将需要的参数传给线程
void trr(Test *test);
};
主程序所需要做的操作:
- 发射信号触发线程;
- 接收信号进行页面标签文本更改;
- 接收线程执行完毕的信号,qDebug()打印“线程执行完毕”字样
在构造函数中:
构造函数中,需要定义一个系统线程对象指定父类为this,再定义自定义线程类不能指定父对象。
最后自定义线程对象调用moveToThread()
函数指定系统线程对象为父类即可。
例:
myThread = new MyThread;
thread = new QThread(this);
// 自定义线程指定旧线程为父类
myThread->moveToThread(thread);
最后在发射信号前使用系统线程thread->start();
就可以执行线程啦。
发射的信号可以不带参数,但是建议带参,这样可以更好的理解信号对应需要绑定哪个槽函数。
代码
下面是例子全部代码
(代码有详细注释)
线程类
MyThread.h
#pragma once
#include <QObject>
//#include "Test.h"
class Test;
class MyThread : public QObject
{
Q_OBJECT
public:
MyThread();
~MyThread();
// 主程序触发线程执行的槽方法(参数必须和关联的信号的参数一致)
void triggerFunc(Test *test);
signals:
void trigger();
private:
//Test *test;
};
MyThread.cpp
#include "MyThread.h"
#include "Test.h"
MyThread::MyThread() {
//test = new Test;
}
MyThread::~MyThread() {
/*if (test)
delete test;*/
}
void MyThread::triggerFunc(Test *test) {
// 根据传过来的参数,调用方法执行
test->test();
// 执行完发射信号,线程已经执行完毕
emit trigger();
}
需要执行操作的类
Test.h
#pragma once
#include <QWidget>
class Test : public QWidget
{
Q_OBJECT
public:
Test();
~Test();
// 真正需要执行的操作
void test();
// 控制方法的运行停止
void setFlags(bool b);
signals:
// 给主程序发射的信号
void uis();
private:
// 控制变量
bool flags;
};
Test.cpp
#include "Test.h"
#include <Windows.h>
#include <QDebug>
Test::Test() {
flags = false;
}
Test::~Test() {
}
void Test::test() {
static int i = 0;
while (!flags) {
Sleep(200); // 休眠200ms
emit uis(); // 发射信号,主程序响应同时做一些操作
qDebug() << i++;
if (flags == true) {
break;
}
}
}
void Test::setFlags(bool b) {
flags = b;
}
主程序类
TestQThreadThree.h
#pragma once
#include <QtWidgets/QWidget>
#include "ui_TestQThreadThree.h"
#include "MyThread.h"
#include "Test.h"
#include <QThread>
class TestQThreadThree : public QWidget {
Q_OBJECT
public:
TestQThreadThree(QWidget *parent = Q_NULLPTR);
~TestQThreadThree();
// 设置Lable标签文本
void setLabel();
signals:
// 信号带参,将需要的参数传给线程
void trr(Test *test);
private slots:
void on_startBtn_clicked(); // 开始按钮槽方法
void on_pauseBtn_clicked(); // 停止按钮槽方法
private:
Ui::TestQThreadThreeClass ui;
// 自定义线程
MyThread *myThread;
// 系统线程
QThread *thread;
// 所需要执行操作的方法类
Test *test;
};
TestQThreadThree.cpp
#include "TestQThreadThree.h"
#include <QDebug>
#pragma execution_character_set("utf-8") // qt支持显示中文
TestQThreadThree::TestQThreadThree(QWidget *parent)
: QWidget(parent) {
ui.setupUi(this);
test = new Test;
myThread = new MyThread; // 不能指定父类
thread = new QThread(this);
// 自定义线程指定旧线程为父类
myThread->moveToThread(thread);
// 主程序发射信号,线程响应执行槽函数
connect(this, &TestQThreadThree::trr, myThread, &MyThread::triggerFunc);
// test对象发射信号,主程序响应执行槽函数
connect(test, &Test::uis, this, &TestQThreadThree::setLabel);
// 线程执行完毕发射信号,主线程响应,知道线程已经执行完毕
connect(myThread, &MyThread::trigger,
[=]() {
QString str = "线程执行完毕!!!";
//ui.numberLabel->setText(str);
qDebug() << str;
});
// 当点击右上角叉时,关闭线程
//connect(this, &TestQThreadThree::destroyed,
//[=]() {
// if (thread->isRunning()) {
// // 停止线程
// test->setFlags(true);
// /* - 推荐使用 - */
// // 停止线程
// thread->quit();
// // 等待线程处理完当前工作
// thread->wait();
// // 强制关闭线程,线程没有执行完也进行强制关闭,不推荐使用
// // thread->terminate();
// }
// // 释放线程
// delete thread;
// delete myThread;
// delete test;
//});
}
TestQThreadThree::~TestQThreadThree() {
/*注意需要停止线程后才进行线程资源的释放*/
if (thread->isRunning()) {
// 停止线程
test->setFlags(true);
/* - 推荐使用 - */
// 停止线程
thread->quit();
// 等待线程处理完当前工作
thread->wait();
// 强制关闭线程,线程没有执行完也进行强制关闭,不推荐使用
// thread->terminate();
}
// 释放线程
delete thread;
delete myThread;
delete test;
}
// isRunning():线程如果启动,返回true,否则返回false
void TestQThreadThree::on_startBtn_clicked() {
// 判断线程是否在运行
if (thread->isRunning() == true) {
return;
}
// 启动线程,但是没有启动线程处理函数
thread->start();
test->setFlags(false);
// 主程序发射信号,线程才进行真正的操作
emit trr(test);
}
void TestQThreadThree::on_pauseBtn_clicked() {
if (thread->isRunning() == false) {
return;
}
test->setFlags(true);
// 停止线程
thread->quit();
// 等待线程处理完当前工作
thread->wait();
}
void TestQThreadThree::setLabel() {
static int i = 0;
ui.numberLabel->setText(QString("%1").arg(++i));
}
运行截图:
点击开始后:
点击暂停后:
注意事项:
- 线程关闭有两种方式:第一种是强制关闭
thread->terminate();
;强制关闭线程,线程没有执行完也进行强制关闭,不推荐使用。第二种是等待线程执行完毕后才进行关闭,thread->quit(); thread->wait();
,推荐使用。 - 检查线程是否在运行状态可以使用
thread->isRunning();
进行判断,线程如果启动,返回true,否则返回false。
总结:
感觉这篇博客写失败了,没有详细的写清楚线程是怎么回事,理解力强的小伙伴看我画的那幅三角恋图,在结合代码进行理解即可。
简单来讲线程就是执行数据处理,主程序触发信号给线程去调用函数执行。