Qt学习:Qt 进程和线程之四,线程实际应用

这篇具有很好参考价值的文章主要介绍了Qt学习:Qt 进程和线程之四,线程实际应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程。对于耗时操作如果不使用线程,UI 界面将会长时间处于停滞状态,这种情况是用户非常不愿意看到的,我们可以用线程来解决这个问题。

大多数情况下,多线程耗时操作会与 UI 进行交互,比如:显示进度、加载等待。。。让用户明确知道目前的状态,并对结果有一个直观的预期,甚至有趣巧妙的设计,能让用户爱上等待,把等待看成一件很美好的事。

一、多线程操作 UI 界面的示例

下面,是一个使用多线程操作 UI 界面的示例 - 更新进度条,采用子类化 QThread 的方式。与此同时,分享在此过程中有可能遇到的问题及解决方法。

首先创建 QtGui 应用,工程名称为 “myThreadBar”,类名选择 “QMainWindow”,其他选项保持默认即可。再添加一个名称为 WorkerThread 的头文件,定义一个 WorkerThread 类,让其继承自 QThread,并重写 run () 函数,修改 workerthread.h 文件如下:

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

#include <QThread>
#include <QDebug>

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent)
    {
        qDebug() << "Worker Thread : " << QThread::currentThreadId();
    }

protected:
    virtual void run() Q_DECL_OVERRIDE
    {
        qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
        int nValue = 0;
        while (nValue < 100)
        {
            // 休眠50毫秒
            msleep(50);
            ++nValue;

            // 准备更新
            emit resultReady(nValue);
        }
    }

signals:
    void resultReady(int value);
};

#endif // WORKERTHREAD_H

通过在 run () 函数中调用 msleep (50),线程会每隔 50 毫秒让当前的进度值加 1,然后发射一个 resultReady () 信号,其余时间什么都不做。在这段空闲时间,线程不占用任何的系统资源。当休眠时间结束,线程就会获得 CPU 时钟,将继续执行它的指令。在 mainwindow.ui 上添加一个按钮和进度条部件,然后 mainwindow.h 修改如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "workerthread.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // 更新进度
    void handleResults(int value);

    // 开启线程
    void startThread();

private:
    Ui::MainWindow *ui;

    WorkerThread m_workerThread;
};

#endif // MAINWINDOW_H

然后 mainwindow.cpp 修改如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
        
  qDebug() << "Main Thread : " << QThread::currentThreadId();        

    // 连接信号槽
    this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::handleResults(int value)
{
    qDebug() << "Handle Thread : " << QThread::currentThreadId();
    ui->progressBar->setValue(value);
}

void MainWindow::startThread()
{
    WorkerThread *workerThread = new WorkerThread(this);
    this->connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
    // 线程结束后,自动销毁
    this->connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
    workerThread->start();
}

由于信号与槽连接类型默认为 “Qt::AutoConnection”,在这里相当于 “Qt::QueuedConnection”。也就是说,槽函数在接收者的线程(主线程)中执行。

执行程序,“应用程序输出” 窗口输出如下:

Main Thread :  0x3140
Worker Thread :  0x3140
Worker Run Thread :  0x2588
Handle Thread :  0x3140

显然,UI 界面、Worker 构造函数、槽函数处于同一线程(主线程),而 run () 函数处于另一线程(次线程)。

回到顶部

二、避免多次 connect

当多次点击 “开始” 按钮的时候,就会多次 connect (),从而启动多个线程,同时更新进度条。为了避免这个问题,我们先在 mainwindow.h 上添加私有成员变量 "WorkerThread m_workerThread;",然后修改 mainwindow.cpp 如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 连接信号槽
    this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));

    this->connect(&m_workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::handleResults(int value)
{
    qDebug() << "Handle Thread : " << QThread::currentThreadId();
    ui->progressBar->setValue(value);
}

void MainWindow::startThread()
{
    if (!m_workerThread.isRunning())
        m_workerThread.start();
}

不再在 startThread () 函数内创建 WorkerThread 对象指针,而是定义私有成员变量,再将 connect 添加在构造函数中,保证了信号槽的正常连接。在线程 start () 之前,可以使用 isFinished () 和 isRunning () 来查询线程的状态,判断线程是否正在运行,以确保线程的正常启动。

三、优雅地结束线程的两种方法

如果一个线程运行完成,就会结束。可很多情况并非这么简单,由于某种特殊原因,当线程还未执行完时,我们就想中止它。

不恰当的中止往往会引起一些未知错误。比如:当关闭主界面的时候,很有可能次线程正在运行,这时,就会出现如下提示:

QThread: Destroyed while thread is still running

这是因为次线程还在运行,就结束了 UI 主线程,导致事件循环结束。这个问题在使用线程的过程中经常遇到,尤其是耗时操作。大多数情况下,当程序退出时,次线程也许会正常退出。这时,虽然抱着侥幸心理,但隐患依然存在,也许在极少数情况下,就会出现 Crash。

所以,我们应该采取合理的措施来优雅地结束线程,一般思路:

  1. 发起线程退出操作,调用 quit () 或 exit ()。

  2. 等待线程完全停止,删除创建在堆上的对象。

  3. 适当的使用 wait ()(用于等待线程的退出)和合理的算法。

方法一

这种方式是 Qt4.x 中比较常用的,主要是利用 “QMutex 互斥锁 + bool 成员变量” 的方式来保证共享数据的安全性。在 workerthread.h 上继续添加互斥锁、析构函数和 stop () 函数,修改如下:

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

#include <QThread>
#include <QMutexLocker>
#include <QDebug>

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent),
          m_bStopped(false)
    {
        qDebug() << "Worker Thread : " << QThread::currentThreadId();
    }

    ~WorkerThread()
    {
        stop();
        quit();
        wait();
    }

    void stop()
    {
        qDebug() << "Worker Stop Thread : " << QThread::currentThreadId();
        QMutexLocker locker(&m_mutex);
        m_bStopped = true;
    }

protected:
    virtual void run() Q_DECL_OVERRIDE 
    {
        qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
        int nValue = 0;
        while (nValue < 100)
        {
            // 休眠50毫秒
            msleep(50);
            ++nValue;

            // 准备更新
            emit resultReady(nValue);

            // 检测是否停止
            {
                QMutexLocker locker(&m_mutex);
                if (m_bStopped)
                    break;
            }
            // locker超出范围并释放互斥锁
        }
    }
    
signals:
    void resultReady(int value);

private:
    bool m_bStopped;
    QMutex m_mutex;
};

#endif // WORKERTHREAD_H

当主窗口被关闭,其 “子对象” WorkerThread 也会析构调用 stop () 函数,使 m_bStopped 变为 true,则 break 跳出循环结束 run () 函数,结束进程。当主线程调用 stop () 更新 m_bStopped 的时候,run () 函数也极有可能正在访问它(这时,他们处于不同的线程),所以存在资源竞争,因此需要加锁,保证共享数据的安全性。

为什么要加锁?

很简单,是为了共享数据段操作的互斥。避免形成资源竞争的情况(多个线程有可能访问同一共享资源的情况)。

方法二

Qt5 以后,可以使用 requestInterruption ()、isInterruptionRequested () 这两个函数,使用很方便,修改 workerthread.h 文件如下:

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

#include <QThread>
#include <QMutexLocker>
#include <QDebug>

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = nullptr)
        : QThread(parent)
    {
        qDebug() << "Worker Thread : " << QThread::currentThreadId();
    }

    ~WorkerThread()
    {
        // 请求终止
        requestInterruption();
        quit();
        wait();
    }

protected:
    virtual void run() Q_DECL_OVERRIDE
    {
        qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
        int nValue = 0;

        // 是否请求终止
        while (!isInterruptionRequested())
        {
            while (nValue < 100)
            {
                // 休眠50毫秒
                msleep(50);
                ++nValue;

                // 准备更新
                emit resultReady(nValue);
            }
        }

    }
signals:
    void resultReady(int value);
};

#endif // WORKERTHREAD_H

在耗时操作中使用 isInterruptionRequested () 来判断是否请求终止线程,如果没有,则一直运行;当希望终止线程的时候,调用 requestInterruption () 即可。这两个函数内部也使用了互斥锁 QMutex。文章来源地址https://www.toymoban.com/news/detail-635705.html

到了这里,关于Qt学习:Qt 进程和线程之四,线程实际应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于进程、线程、协程的概念以及Java中的应用

    本文将从“操作系统”、“Java应用”上两个角度来探究这三者的区别。 在我本人的疑惑中,我有以下3个问题。 在“多道程序环境下”,允许多个程序并发执行,此时它们将失去封闭性,并具有间断性以及不可再现性的特征,因此需要引入进程的概念。 进程是程序执行的过

    2024年02月08日
    浏览(62)
  • (学习笔记-进程管理)线程

    在早期的操作系统都是以进程为独立运行的基本单位,直到后面,计算机科学家们提出了更小的能独立运行的基本单位: 线程   举个例子,假设要编写一个视频播放软件,那么软件功能的核心模块有三个: 从视频文件中读取数据 对读取的数据进行解压缩 把压缩后的视频数

    2024年02月14日
    浏览(41)
  • Python学习笔记——PySide6设计GUI应用之UI与逻辑分离

    1、打开PySide6的UI设计工具pyside6-designer,设计一个主窗口,保存文件名为testwindow.ui 2、使用PySide6的RCC工具把testwindow.ui文件转换为testwindow_rc.py文件,此文件中有一个类Ui_MainWindow(包含各种控件对象) 一、通过类继承实现: class TestMainWindow(QMainWindow, Ui_MainWindow): 定义了一个新的

    2024年04月16日
    浏览(56)
  • 深度学习实战应用:分享一些深度学习在实际问题中的应用案例和经验

    目录 一、引言 二、案例一:计算机视觉——图像分类

    2024年02月02日
    浏览(44)
  • (学习笔记-进程管理)多线程冲突如何解决

    对于共享资源,如果没有上锁,在多线程的环境里,很有可能发生翻车。 在单核 CPU 系统里,为了实现多个程序同时运行的假象,操作系统通常以时间片调度的方式,让每个进程每次执行一个时间片,时间片用完了,就切换下一个进程运行,由于这个时间片的时间很短,于是

    2024年02月13日
    浏览(129)
  • 对于学习Linux进程与线程的感悟

    进程感觉就像一个应用程序一样,比如QQ,火狐浏览器等等,他们之间互不干扰,可以独立运行。线程就像QQ里的各种功能,比如好友列表,显示当前是在线还是离线,会话窗口等等去实现各种功能,进程死掉的话,这些线程也会跟着结束。 经过一段时间的学习,发现线程方

    2024年02月08日
    浏览(36)
  • 数据的语言:学习数据可视化的实际应用

    数据可视化应该学什么?这是一个在信息时代越来越重要的问题。随着数据不断增长和积累,从社交媒体到企业业务,从科学研究到医疗健康,我们都面临着海量的数据。然而,数据本身往往是冰冷、抽象的数字,对于大多数人而言,很难从中获得实质性的信息。这时,数据

    2024年02月11日
    浏览(38)
  • JUC并发编程学习笔记(一)认知进程和线程

    进程 一个程序,如QQ.exe,是程序的集合 一个进程往往可以包含多个线程,至少包含一个 java默认有两个线程,GC垃圾回收线程和Main线程 线程:一个进程中的各个功能 java无法真正的开启线程,因为java是运行在虚拟机上的,所以只能通过C++,通过native本地方法调用C++开启线程

    2024年02月06日
    浏览(55)
  • 【从零学习python 】84.深入理解线程和进程

    进程,能够完成多任务,比如在一台电脑上能够同时运行多个QQ。 线程,能够完成多任务,比如一个QQ中的多个聊天窗口。 进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程

    2024年02月11日
    浏览(39)
  • QT初始学习中的个人基础认知

    安装的时候 感觉更像 python的库安装和编译器版本的配合安装 。进入 创建工程 时,感觉是c++语言的创建工程的感觉,而且可以看到main和h的头文件, 整体来看是C++来编写的程序 。完成整个工程个人感觉是 C++编写功能,使用VB实现界面设计。 但第一眼见QT的UI界面,感觉这*

    2024年02月10日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包