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

news2025/2/24 21:12:43

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_OBJECT

public:
    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_OBJECT

private:
    /* 互斥锁 */
    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.coloradmin.cn/o/743522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

注解和反射02(Java)

反射机制 首先需了解静态语言和动态语言。 动态语言是一类在运行时可以改变其结构的语言&#xff1a;例如新的函数、对象、甚至代码可以被引进&#xff0c;已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言&a…

一文解析Arm64 栈回溯

AArch64栈的结构 Arm64有4种栈&#xff0c;分别是空增栈(Empty Ascendant Stack,EA)、空减栈(Empty Descendant Stack,ED)、满增栈(Full Ascendant Stack,FA)、满减栈(Full Descendant Stack,FD)。常用的是满减栈&#xff0c;Linux内核也使用满减栈。 下图是一个满减栈的示意…

AppSpider Pro 7.4.053 for Windows - Web 应用程序安全测试

AppSpider Pro 7.4.053 for Windows - Web 应用程序安全测试 Rapid7 Dynamic Application Security Testing (DAST) 请访问原文链接&#xff1a;https://sysin.org/blog/appspider/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin…

分板机视觉定位切割软硬件方案

【检测目的】 定位切割 【拍摄效果图一】 【拍摄效果图二】 【拍摄效果图三】 【方案评估】 以目前样品进行实验来看&#xff0c;图像效果明显&#xff0c;可以找到中线位置。 视野&#xff1a;44mm*33mm 视觉精度&#xff1a;44mm/2448pixel0.018mm/pixel。 【硬件配置】…

抖音seo源码.视频剪辑功能开发(一)

一、短视频抖音seo账号矩阵系统 批量剪辑功能的开发一般有以下几种方式 1. 前端实现&#xff1a;通过前端技术&#xff0c;利用vue jquery layui JavaScript&#xff0c;等语言&#xff0c;实现一个可视化的编辑器&#xff0c;用户可以批量上传视频文件&#xff0c;设置剪…

uniapp-日历控件

第一步&#xff1a;打开uniapp的插件市场 网址&#xff1a;日历组件可选择周与月标记打卡支持左右切换 - DCloud 插件市场 第二步&#xff1a;导入相应的项目&#xff0c;会有相应的提示&#xff08;路径&#xff09; 第三步&#xff1a;引入对应的位置-例如我引入的位置 imp…

fastadmin视图渲染

基类app\common\controller\Backend会默认渲染以下几个对象到视图中 //渲染站点配置 $this->assign(site, $site); //渲染配置信息 $this->assign(config, $config); //渲染权限对象 $this->assign(auth, $this->auth); //渲染管理员对象 $this->assign(admin,…

ArcGISPro加载在线底图和影像

经常用ArcGIS都知道,在工作中配合在线地图有点多爽。无论是制图还是数据校核都非常方便。之前已经讲过如何在ArcGIS地图里利用simplegis插件加载多种在线地图,那换成pro咋办嘞 今天我们就来说说如何在ArcGIS Pro里加载在线地图 ArcGISPro本身就自带了两种影像,均是源自谷歌…

《Redis 核心技术与实战》课程学习笔记(八)

String 类型为什么不好用了&#xff1f; String 类型可以保存二进制字节流&#xff0c;只要把数据转成二进制字节数组&#xff0c;就可以保存了。String 类型并不是适用于所有场合的&#xff0c;它有一个明显的短板&#xff0c;就是它保存数据时所消耗的内存空间较多。 为什么…

不平衡电网条件下基于变频器DG操作的多目标优化研究(Matlab代码Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

贪心算法、贪心搜索/采样(greedy search/sampling)、集束搜索(beam search)、随机采样(random sample)

首先需要了解贪心算法&#xff1a; 贪心算法&#xff0c;又名贪婪法&#xff0c;是寻找最优解问题的常用方法&#xff0c;这种方法模式一般将求解过程分成若干个步骤&#xff0c;但每个步骤都应用贪心原则&#xff0c;选取当前状态下最好/最优的选择&#xff08;局部最有利的选…

Tenable Nessus 10.5.3 (Unix, Linux, Windows) - #1 漏洞评估解决方案

Tenable Nessus 10.5.3 (Unix, Linux, Windows) - #1 漏洞评估解决方案 发布 Nessus 试用版自动化安装程序&#xff0c;支持 macOS Ventura、RHEL 9 和 Ubuntu 22.04 请访问原文链接&#xff1a;https://sysin.org/blog/nessus-10/&#xff0c;查看最新版。原创作品&#xff…

开源堡垒机Guacamole二次开发记录之二

这篇主要记录录屏和SFTP的实现。 录屏及视频播放 对于录屏及录屏的播放&#xff0c;因为我们的项目中需要把guacd和java后端分开两台服务器部署&#xff0c;而guacamole的录屏是通过guacd程序录制的。我的要求是在Java后端直接把录好的视频文件通过http前端播放&#xff0c;因…

手机外壳缺陷视觉检测软硬件方案

单独使用一种光源效果图 同轴光会出现亮度不够的情况&#xff1b;回形面光因为光源中间的圆孔会使图像有阴影&#xff0c;造成图像效果不均衡&#xff0c;所以不采用单独光源打光 使用同轴回形面光源效果图 回形光源照亮产品要寻找的边缘&#xff0c;同轴光源起到补光的作用&a…

裁剪opencv库到2Mb

摘要&#xff1a;本文描述了如何对opencv进行裁剪已达到最小化&#xff0c;不限于使用模块编译&#xff0c;去除第三方库依赖&#xff0c;改变编译选项&#xff0c;限制导出符号等。   关键字&#xff1a;opencv、导出符号 opencv库大小优化的文章网络上很少&#xff0c;大部…

【C++ 学习 ⑩】- 详解 string 类(下):string 类的模拟实现和写时拷贝

目录 一、string 类的模拟实现 1.1 - string.h 1.2 - test.cpp 二、string 类的写时拷贝 2.1 - 示例 2.2 - 原理 一、string 类的模拟实现 1.1 - string.h #pragma once#include <assert.h> #include <string.h> #include <iostream>namespace yzz {…

mac版android studio设置字体避坑总结

1.整体主题字体设置: setting->Appearance & Behavior->Appearance->Theme: 设置主题 Use custom font:右边的数字是设置除了编辑代码去之外的字体大小 ,推荐使用AppleSystemUIFont 注意这个字体有个bug,就是如果用在终端横向会有空格: 2.设置终端字体: setting-…

PyTorch深度学习实战(5)——计算机视觉

PyTorch深度学习实战&#xff08;5&#xff09;——计算机视觉 0. 前言1. 图像表示2. 将图像转换为结构化数组2.1 灰度图像表示2.2 彩色图像表示 3 利用神经网络进行图像分析的优势小结系列链接 0. 前言 计算机视觉是指通过计算机系统对图像和视频进行处理和分析&#xff0c;利…

云计算基础教程(第2版)笔记——基础篇与技术篇介绍

文章目录 前言 第一篇 基础篇 一 绪论 1.1 云计算的概念以及特征 1.1.1云计算的基本概念 1.1.2云计算的基本特征 1.2 云计算发展简史 1.3 三种业务模式介绍 1. 基础设施即服务&#xff08;IaaS&#xff09; 2. 平台即服务&#xff08;PaaS&#xff09; 3. 软…

TypeScript 学习笔记(二):接口

一、接口的定义 在面向对象的编程中&#xff0c;接口是一种规范的定义&#xff0c;它定义了行为和动作的规范&#xff0c;在程序设计里面&#xff0c;接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范&#xff0c;接口不关心这些类的内部状态数据&#xff0…