【嵌入式Qt开发入门】Qt如何使用多线程——继承QObject的线程

QObject   

     在上篇已经说过,继承 QThread 类是创建线程的一种方法,另一种就是继承 QObject 类。继承 QObject 类更加灵活。它通过 QObject::moveToThread()方法,将一个 QObeject 的类转移到一个线程里执行,可以通过下图理解。

         通过上面的图不难理解,首先我们写一个类继承 QObject,通过 QObject::moveToThread() 方法将它移到一个 QThread 线程里执行。那么可以通过主线程发送信号去调用 QThread 线程的 方法如上图的 fun4(),fun5()等等。这些方法都是在 QThread 线程里执行的。

应用实例

        项目名称:qthread_example2  

        快速了解继承 QObject 类线程的使用,继承 QObject类的线程。通过 QObject 类继承线程,然后在 MainWindow 类里使用。通过点击一个按钮开启线程。另一个按钮点击关闭线程。另外通过加锁的操作来安全的终止一个线程。(我们可以通过 QMutexLocker 可以安全的使用 QMutex 以免忘记解锁)

        我们谈谈为什么需要加锁来终止一个线程?因为 quit()和 exit()方法都不会中途终止线程。 要马上终止一个线程可以用 terminate()方法。但是这个函数存在非常不安全的因素,Qt 官方文档说不推荐使用。

        我们可以添加一个 bool 变量,通过主线程修改这个 bool 变量来终止,但是有可能引起访问冲突,所以需要加锁,例程里可能体现不是那么明确,当我们有 doWork1(),doWork2…就能体现到 bool 变量加锁的作用了。但是加锁会消耗一定的性能,增加耗时。

        下面的例子是仿照 Qt 官方写的,流程图如下所示。

 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QThread>
#include <QDebug>
#include <QPushButton>
#include <QMutexLocker>
#include <QMutex>/* 工人类 */
class Worker;class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:/* 开始线程按钮 */QPushButton *pushButton1;/* 打断线程按钮 */QPushButton *pushButton2;/* 全局线程 */QThread workerThread;/* 工人类 */Worker *worker;private slots:/* 按钮1点击开启线程 */void pushButton1Clicked();/* 按钮2点击打断线程 */void pushButton2Clicked();/* 用于接收工人是否在工作的信号 */void handleResults(const QString &);signals:/* 工人开始工作(做些耗时的操作 ) */void startWork(const QString &);
};/* Worker类,这个类声明了doWork1函数,将整个Worker类移至线程workerThread */
class Worker : public QObject
{Q_OBJECTprivate:/* 互斥锁 */QMutex lock;/* 标志位 */bool isCanRun;public slots:/* 耗时的工作都放在槽函数下,工人可以有多份不同的工作,但是每次只能去做一份 */void doWork1(const QString &parameter) {/* 标志位为真 */isCanRun = true;/* 死循环 */while (1) {/* 此{}作用是QMutexLocker与lock的作用范围,获取锁后,* 运行完成后即解锁 */{QMutexLocker locker(&lock);/* 如果标志位不为真 */if (!isCanRun) {/* 跳出循环 */break;}}/* 使用QThread里的延时函数,当作一个普通延时 */QThread::sleep(2);emit resultReady(parameter + "doWork1函数");}/* doWork1运行完成,发送信号 */emit resultReady("打断doWork1函数");}// void doWork2();...public:/* 打断线程(注意此方法不能放在槽函数下) */void stopWork() {qDebug()<<"打断线程"<<endl;/* 获取锁后,运行完成后即解锁 */QMutexLocker locker(&lock);isCanRun = false;}signals:/* 工人工作函数状态的信号 */void resultReady(const QString &result);
};
#endif // MAINWINDOW_H

        首先,声明一个 Worker 的类继承 QObject 类,这里是参考 Qt 的 QThread 类的帮助文档的写法。将官方的例子运用到我们的例子里去。

        然后,我们把耗时的工作都放于槽函数下。工人可以有不同的工作,但是每次只能去做一份。这里不同于继承 QThread 类的线程 run(),继承 QThread 的类只有 run()在新线程里。 而继承 QObject 的类,使用 moveToThread()可以把整个继承的 QObject 类移至线程里执行,所以可以有 doWork1(),doWork2()等耗时的操作,但是这些耗时的操作都应该作为槽函数,由主线程去调用。

        最后,进入循环后使用互斥锁判断 isCanRun 标识符的状态,为假即跳出 while 循环,直到 doWork1 结束。注意,虽然 doWork1 结束了,但是线程并没有退出(结束)。因为我们把这个类移到线程里了,直到这个类被销毁。或者使用 quit()和 exit()退出线程才真正的结束!

        在源文件“mainwindow.cpp”具体代码如下。

#include "mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent)
{/* 设置显示位置与大小 */this->setGeometry(0, 0, 800, 480);pushButton1 =  new QPushButton(this);pushButton2 =  new QPushButton(this);/* 设置按钮的位置大小 */pushButton1->setGeometry(300, 200, 80, 40);pushButton2->setGeometry(400, 200, 80, 40);/* 设置两个按钮的文本 */pushButton1->setText("开启线程");pushButton2->setText("打断线程");/* 工人类实例化 */worker = new Worker;/* 将worker类移至线程workerThread */worker->moveToThread(&workerThread);/* 信号槽连接 *//* 线程完成销毁对象 */connect(&workerThread, SIGNAL(finished()),worker, SLOT(deleteLater()));connect(&workerThread, SIGNAL(finished()),&workerThread, SLOT(deleteLater()));/* 发送开始工作的信号,开始工作 */connect(this, SIGNAL(startWork(QString)),worker, SLOT(doWork1(QString)));/* 接收到worker发送过来的信号 */connect(worker, SIGNAL(resultReady(QString)),this, SLOT(handleResults(QString)));/* 点击按钮开始线程 */connect(pushButton1, SIGNAL(clicked()),this, SLOT(pushButton1Clicked()));/* 点击按钮打断线程 */connect(pushButton2, SIGNAL(clicked()),this, SLOT(pushButton2Clicked()));
}MainWindow::~MainWindow()
{/* 打断线程再退出 */worker->stopWork();workerThread.quit();/* 阻塞线程2000ms,判断线程是否结束 */if (workerThread.wait(2000)) {qDebug()<<"线程结束"<<endl;}
}void MainWindow::pushButton1Clicked()
{/* 字符串常量 */const QString str = "正在运行";/* 判断线程是否在运行 */if(!workerThread.isRunning()) {/* 开启线程 */workerThread.start();}/* 发送正在运行的信号,线程收到信号后执行后返回线程耗时函数 + 此字符串 */emit this->startWork(str);
}void MainWindow::pushButton2Clicked()
{/* 如果线程在运行 */if(workerThread.isRunning()) {/* 停止耗时工作,跳出耗时工作的循环 */worker->stopWork();}
}void MainWindow::handleResults(const QString & results)
{/* 打印线程的状态 */qDebug()<<"线程的状态:"<<results<<endl;
}

       首先,实例化工人类。继承 QObject 的多线程类不能指定父对象。

        工人类实例化后,工人类将自己移至 workerThread 线程里执行。

        线程结束后,我们需要使用 deleteLater 来销毁 worker 对象和 workerThread 对象分配的内存。deleteLater 会确认消息循环中没有这两个线程的对象后销毁。

程序运行效果

        点击开启线程按钮后,应用程序输出窗口每隔 2 秒打印“正在运行 doWork1 函数”,当我们点击打断线程按钮后,窗口打印出“打断 doWork1 函数”。点击打断线程,会打断 doWork1函数的循环,doWork1 函数就运行结束了。再点击开启线程,可以再次运行 doWork1 函数。本例界面简单,仅用了两个按钮和打印语句作为显示部分,但是对初学线程的朋友们友好,因为程序不长。我们可以结合程序的注释,一步步去理解这种线程的写法。重要的是掌握写法,最后才应用到花里胡哨的界面去吧!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/18547.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6.2.1 网络基本服务---域名解析系统DNS

6.2.1 网络基本服务—域名解析系统DNS 因特网是需要提供一些最基本的服务的&#xff0c;今天我们就来讨论一下这些基本的服务。 域名系统&#xff08;DNS&#xff09;远程登录&#xff08;Telnet&#xff09;文件传输协议&#xff08;FTP&#xff09;动态主机配置协议&#x…

机器学习之随机森林(Random forest)

1 什么是随机森林 随机森林是一种监督式算法&#xff0c;使用由众多决策树组成的一种集成学习方法&#xff0c;输出是对问题最佳答案的共识。随机森林可用于分类或回归&#xff0c;是一种主流的集成学习算法。 1.1 随机森林算法原理 随机森林中有许多的分类树。我们要将一个输…

Android Studio无法打开问题解决记录

目录 1 问题起因2 发现问题3 解决问题 1 问题起因 问题的起因是我为了运行一个Kotlin项目&#xff0c;但是报了一个错误&#xff1a; Kotlin报错The binary version of its metadata is 1.5.1, expected version is 1.1.16 然后我就上百度去搜了以下&#xff0c;一篇博客让禁用…

echarts饼图设置颜色的两种方式

1. 直接写在color数组中 option {color:[#fac858,#e0504b,#e6e9ee],series: {type: pie,radius: [40%, 70%],data: [{ value: 1048, name: Search Engine, },{ value: 735, name: Direct},{ value: 580, name: Email },]} };2. 在series.data.itemStyle.color中 option {se…

ofd文件怎么打开?试试3个打开方法

什么是ofd文件&#xff1f; 很多朋友对ofd文件也许不太了解&#xff0c;它实际上就是开放版式文件的意思&#xff0c;即&#xff08;Open Fixed-layout documents&#xff09;的缩写。ofd文件与PDF文件很类似。都是有独立格式、版面固定的特点的。在我们日常生活中&#xff0c;…

langchain调用chatGLM2纪实

一、科学上网要注意&#xff1a; 域名全代和全局代理&#xff08;网卡&#xff09;&#xff0c;都要打开。这样conda install特别快。 二、安装langchain 1、 conda install langchain 2、 conda install openai 注意&#xff1a; 使用pip install和conda install 是不同…

Vue3之app.config.globalProperties(定义全局变量)

使用之因 一般我们在vue开发中&#xff0c;常用的功能&#xff0c;接口等等我们都会封装起来&#xff0c;如何每次创建一个组件&#xff0c;想要使用这些封装起来的功能、接口等等都需要先引入&#xff0c;再通过层层调用才可以得到结果&#xff0c;如果我现在一遍需要调用后端…

C语言 指针进阶(二)

目录 一.函数指针 1.1函数指针的认识 1.2函数指针的使用 二、函数指针数组 1.1函数指针的认识 1.2 函数指针数组实现计算器 三、指向函数指针数组的指针 四、回调函数 通过使用qsort函数加强对回调函数的理解 qsort排序整形 qosrt排序结构体 用冒泡排序的思想&…

kafka第一课-Kafka快速实战以及基本原理详解

一、Kafka介绍 Kafka是一个分布式的发布-订阅消息系统&#xff0c;可以快速地处理高吞吐量的数据流&#xff0c;并将数据实时地分发到多个消费者中。Kafka消息系统由多个broker&#xff08;服务器&#xff09;组成&#xff0c;这些broker可以在多个数据中心之间分布式部署&…

pandas 笔记:高亮内容

1 高亮缺失值 1.0 数据 import pandas as pd import numpy as npdata[{a:1,b:2},{a:3,c:4},{a:10,b:-2,c:5}]df1pd.DataFrame(data) df1 1.1 highlight_null df.style.highlight_null(color: str red,subset: Subset | None None,props: str | None None, ) 1.1.1 默认情…

uniapp在微信开放平台创建移动应用时,如何生成应用签名的问题

包名在打包的时候是必填项&#xff0c;就不多赘述了… 微信开放平台获取应用签名&#xff0c; 场景&#xff1a; 首先需要在手机或者模拟器上下载签名生成工具&#xff0c;下载地址&#xff1a;下载签名生成工具 然后手机打开&#xff0c; 在这里输入你的app打包时的包名&…

Acwing 849. Dijkstra求最短路 I

Acwing 849. Dijkstra求最短路 I 链接:849. Dijkstra求最短路 I - AcWing题库 /*题解:dijkstra算法模板对于单源最短路径dijkstra1.每次找到当前距离源最近的节点 作为确定距离的点2.通过这个点看能否让其他的节点来松弛其他点到源的距离重复12操作*/ #include<algorithm&g…