Qt 线程

💡 进度条显示拷贝进度(verson 1)

窗口上放置一个按钮和一个进度条部件,点击按钮,进行拷贝操作 ------ 打开对话框选择源文件,然后再打开一个对话框 选择 目标文件存放位置和名称。拷贝过程中进度条显示当前进度(大文件)。不允许用QFile 的 copy 方法 ------ 把源文件分解开,然后一帧一帧写入目标文件。

widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QtWidgets>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_released();

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

#define KB 1024

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

    ui->progressBar->setValue(0);
}

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

void Widget::on_pushButton_released()
{
    // 1.选定源文件
    QString srcName = QFileDialog::getOpenFileName(this, tr("选择文件"), \
    							"D:/Packages_", tr("all  (*.*)"));
    if (srcName.isEmpty())
        return ;
    qDebug() << "src: " << srcName;

    // 2.选定目标文件
    QString destName = QFileDialog::getSaveFileName(this, tr("保存文件"), \
    							"e:", tr("all  (*.*)"));
    if (destName.isEmpty())
        return ;
    qDebug() << "dest: " << destName;

    
    QFile srcFile(srcName);
    if (!srcFile.open(QIODevice::ReadOnly))
    {
        qDebug() << "打开文件失败";
        return ;
    }

    QFile destFile(destName);
    if (!destFile.open(QIODevice::ReadWrite | QIODevice::Truncate))
    {
        qDebug() << "复制文件失败";
        return ;
    }

    
    ui->progressBar->setValue(0);			// 确保每次点击按钮时,进度条都是从 0 开始
    qint64 totalSize = srcFile.size();
    qint64 currentSize = 0;
    QByteArray buffer;

    // 3.开始拷贝--不断读取,然后写入
    while (!srcFile.atEnd())
    {
        buffer.clear();
        buffer = srcFile.read(KB);
        destFile.write(buffer);

        // 4.更新进度值
        currentSize += buffer.size();
        ui->progressBar->setValue(100 * currentSize / totalSize);
    }
}

运行结果如下:(复制文件时,拖动窗口会产生卡顿)

QT 线程

GUI 线程(QT 的主线程)

QT 的主线程称为 GUI 线程,负责初始化界面监听事件循环,并根据事件处理做出界面上的反馈。

使用多线程的好处

1、提高应用界面的响应速度;

这对于开发图形界面程序尤其重要,当一个操作耗时很长时(比如大批量 I/O 或大量矩阵变换等CPU密集操作),整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而不会影响到 主 GUI 线程,从而避免上述问题。

2、使多核心 CPU 系统更加有效;

当线程数不大于CPU核数时,操作系统可以调度不同的线程运行于不同的CPU核上。

3、改善程序结构;

一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于程序的理解和维护。

创建一个界面,界面 show 以后调用睡眠函数,观察效果。

QThread 类

相关接口

Public Functions:
	QThread(QObject *parent = 0); 			// 构造函数  // pthread_create
	bool 	isFinished() const;  			// 判断线程是否退出
	bool	wait(unsigned long time = ULONG_MAX);   // pthread_join(&id)
	// 等待某个线程结束,最多等待time ms,如果时间没有设置,那么永远等待。

Public Slots:
	void	start(Priority priority = InheritPriority)  // 启动线程必须使用start
	void	terminate();				// 杀死线程  	// pthread_cancel

Static Public Members:
	Qt::HANDLE	currentThreadId() [static] 		// 得到当前执行者线程ID,可以直接qDebug
	void	sleep(unsigned long secs) [static]
	void	msleep(unsigned long msecs) [static]
	void	usleep(unsigned long usecs) [static]

	睡眠函数不能在主线程调用,会造成界面卡死。

Protected Functions:	
	virtual void run();  	// 启动新线程不能直接调用run,需要调用 start 接口,
							// start 会启动新线程,然后执行run里的代码块。

编程流程

1)子类化 QThread(重写一个类,继承自 QThread);

2)重写 run 函数,执行耗时操作(run 函数内有一个 while / for 循环 或 sleep);

3)子线程类实现 公共方法,供主线程传参(主线程给子线程传参);

4)主线程内 定义并实例化子线程的类对象;

5)主线程调用 start 方法 启动子线程;

6)设置必要的 信号和槽 做连接 ------> 子线程给主线程传参;

7)设置一个标记 来控制循环的退出,或者父线程调用 terminate 停止子线程。
注意:子线程内不允许操作界面上的任何部件,所有界面操作都应该由 GUI 主线程来进行

💡 练习

重写一个线程子类,GUI 线程传递一个值给子线程。界面放置一个按钮,点击按钮后线程开始运行。子线程打印主线程传递的值以后睡眠任意时间,线程运行结束后通知主线程自己运行的时间,主线程把子线程睡眠的总时间显示到界面上。

mythread.h
cpp 复制代码
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <qdebug.h>

class MyThread : public QThread					// 1)重写一个类,继承自 QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    void setValue(int num) {this->val = num;}	// 3)子线程类实现 公共方法,供主线程传参

signals:
    void valueSignal(int);						// 6)子线程发信号给主线程

public slots:

private:
    void run();									// 2)重写 run 函数
    int val;
};

#endif // MYTHREAD_H
mythread.cpp
cpp 复制代码
#include "mythread.h"

MyThread::MyThread(QObject *parent) :
    QThread(parent)
{
}

void MyThread::run()					// 2)重写 run 函数
{
    qDebug() << "From parent thread: val = " << val;
    QThread::sleep(16);					// 2)run 函数内有一个 while/for 循环 或 sleep
    emit valueSignal(16);
}
widget.h
cpp 复制代码
#define WIDGET_H

#include <QtWidgets>
#include "mythread.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    Ui::Widget *ui;
    MyThread *myth;							// 4)主线程内 定义子线程的类对象

public slots:
    void valueSlot(int);					// 6)主线程的槽接收来自子线程的信号

private slots:
    void on_pushButton_clicked();
};

#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

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

    // 4)主线程内 实例化子线程的类对象
    myth = new MyThread(this);          // 使用 this 的目的仅是方便回收

    myth->setValue(98);					// 4.5)主线程传参给子线程

    // 6)设置必要的 信号和槽 做连接
    QObject::connect(myth, SIGNAL(valueSignal(int)), this,  SLOT(valueSlot(int)));
}

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

void Widget::valueSlot(int num)			// 6)子线程给主线程传参的方式;信号与槽
{
    ui->label->setText("From child thread: val = " + QString::number(num));
}

void Widget::on_pushButton_clicked()
{
    myth->start();						// 5)主线程调用 start 方法 启动子线程
}

实现效果如下:

💡 进度条显示拷贝进度(verson 2)

newthread.h
cpp 复制代码
#ifndef NEWTHREAD_H
#define NEWTHREAD_H

#include <QThread>
#include <QtWidgets>

class NewThread : public QThread
{
    Q_OBJECT
public:
    explicit NewThread(QObject *parent = 0);
    void setFileNames(QString src, QString dest)
    {
        srcName = src;
        destName = dest;
    }

signals:
    void valueSignal(int);

public slots:

protected:
    void run();
    QString srcName;
    QString destName;
    int percentage;
};

#endif // NEWTHREAD_H
newthread.cpp
cpp 复制代码
#include "newthread.h"

#define KB 1024

NewThread::NewThread(QObject *parent) :
    QThread(parent)
{
}

void NewThread::run()
{
    QFile srcFile(srcName);
    if (!srcFile.open(QIODevice::ReadOnly))
    {
        qDebug() << "打开文件失败";
        return ;
    }

    QFile destFile(destName);
    if (!destFile.open(QIODevice::ReadWrite | QIODevice::Truncate))
    {
        qDebug() << "复制文件失败";
        return ;
    }

    qint64 totalSize = srcFile.size();
    qint64 currentSize = 0;
    QByteArray buffer;

    while (!srcFile.atEnd())
    {
        buffer.clear();
        buffer = srcFile.read(KB);
        destFile.write(buffer);

        currentSize += buffer.size();
        percentage = 100 * currentSize / totalSize;
        emit valueSignal(percentage);
    }
}
widget.h
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QtWidgets>
#include "newthread.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_released();

private:
    Ui::Widget *ui;
    NewThread *thr;

public slots:
    void valueSlot(int);
};

#endif // WIDGET_H
widget.cpp
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

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

    ui->progressBar->setValue(0);

    thr = new NewThread(this);
    QObject::connect(thr, SIGNAL(valueSignal(int)), this,  SLOT(valueSlot(int)));
}

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

void Widget::on_pushButton_released()
{
    QString srcName = QFileDialog::getOpenFileName(this, tr("选择文件"), \
    							"D:/Packages_", tr("all  (*.*)"));
    if (srcName.isEmpty())
        return ;
    qDebug() << "src: " << srcName;

    QString destName = QFileDialog::getSaveFileName(this, tr("保存文件"), \
    							"e:", tr("all  (*.*)"));
    if (destName.isEmpty())
        return ;
    qDebug() << "dest: " << destName;

    ui->progressBar->setValue(0);				// 每次启动线程前,将进度条的进度 置0
    thr->setFileNames(srcName, destName);
    thr->start();								// 不要忘记启动线程
}

void Widget::valueSlot(int value)
{
    ui->progressBar->setValue(value);
}

运行结果如下:(复制文件时,拖动窗口不会产生卡顿)

相关推荐
娅娅梨25 分钟前
C++ 错题本--not found for architecture x86_64 问题
开发语言·c++
兵哥工控29 分钟前
MFC工控项目实例二十九主对话框调用子对话框设定参数值
c++·mfc
汤米粥31 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾34 分钟前
EasyExcel使用
java·开发语言·excel
我爱工作&工作love我37 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
拾荒的小海螺40 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思1 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
lexusv8ls600h1 小时前
探索 C++20:C++ 的新纪元
c++·c++20
lexusv8ls600h1 小时前
C++20 中最优雅的那个小特性 - Ranges
c++·c++20