前言

在以往的项目开发中,在很多地方用到了多线程。针对不同的业务逻辑,需要使用不同的多线程实现方法,来达到优化项目的目的。本文记录下在Qt开发中用到的多线程技术实现方法,以导出指定范围的数字到txt文件为例,展示多线程不同的实现方式。

示例已上传到gittee,地址:https://gitee.com/zbylalalala1/qt_-thread-demo.git

导出文件的示例工具类

首先提供一个工具类,用于将指定范围的数字写入txt文件。

#ifndef UTILITIES_H
#define UTILITIES_H #include <QString>
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDir>
#include <QDebug>
class Utilities
{
public:
static bool writeNumbersToFile(int start, int end, const QString& prefix = "numbers")
{
if (start > end) {
qDebug() << "起始数字不能大于结束数字";
return false;
} // 获取当前时间并格式化为文件名
QDateTime currentTime = QDateTime::currentDateTime();
QString timeString = currentTime.toString("yyyy-MM-dd_hh-mm-ss");
QString fileName = QString("%1_%2_to_%3_%4.txt")
.arg(prefix)
.arg(start)
.arg(end)
.arg(timeString); // 创建文件对象
QFile file(fileName); // 以写入模式打开文件
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "无法创建文件:" << fileName;
return false;
} // 创建文本流
QTextStream out(&file); // 写入指定范围的数字
int count = 0;
for (int i = start; i <= end; ++i) {
out << i;
count++; // 每10个数字换行
if (count % 10 == 0 || i == end) {
out << "\n";
} else {
out << " "; // 数字之间用空格分隔
}
} // 关闭文件
file.close(); qDebug() << "成功写入文件:" << fileName;
qDebug() << "文件路径:" << QDir::currentPath() + "/" + fileName;
qDebug() << "写入数字范围:" << start << "到" << end << ",共" << (end - start + 1) << "个数字"; return true;
} // 获取当前工作目录
static QString getCurrentPath()
{
return QDir::currentPath();
} // 检查文件是否存在
static bool fileExists(const QString& fileName)
{
QFile file(fileName);
return file.exists();
}
}; #endif // UTILITIES_H

QThread

使用QThread类来创建线程,是Qt中最简单的一种多线程实现方式,不过一般不建议使用,因为它的功能比较有限。

使用QThread的方式为:继承QThread并重写run()函数。

** ExportThread.h **

#ifndef EXPORTTHREAD_H
#define EXPORTTHREAD_H #include <QThread>
#include <QDebug>
#include "Utilities.h" class ExportThread : public QThread
{
Q_OBJECT public:
explicit ExportThread(QObject *parent = nullptr); // 设置导出参数
void setExportParams(int start = 1, int end = 10000, const QString& prefix = "numbers"); protected:
void run() override; signals:
void exportStarted();
void exportFinished(bool success, const QString& message);
void progressUpdate(int current, int total); private:
int m_start;
int m_end;
QString m_prefix;
}; #endif // EXPORTTHREAD_H

** ExportThread.cpp **

#include "ExportThread.h"
#include <QDateTime>
#include <QDir> ExportThread::ExportThread(QObject *parent)
: QThread(parent)
, m_start(1)
, m_end(10000)
, m_prefix("numbers")
{
} void ExportThread::setExportParams(int start, int end, const QString& prefix)
{
m_start = start;
m_end = end;
m_prefix = prefix;
} void ExportThread::run()
{
qDebug() << "导出线程开始运行...";
emit exportStarted(); try {
bool success = Utilities::writeNumbersToFile(m_start, m_end, m_prefix);
if (success) {
emit exportFinished(true, QString("文件导出成功!范围:%1-%2").arg(m_start).arg(m_end));
} else {
emit exportFinished(false, "文件导出失败!");
}
qDebug() << "导出线程完成"; } catch (const std::exception& e) {
qDebug() << "导出过程中发生异常:" << e.what();
emit exportFinished(false, QString("导出过程中发生异常: %1").arg(e.what()));
}
}

使用方式:

ExportThread *exportThread = new ExportThread(this);
exportThread->setExportParams(1, 10000, "numbers");
exportThread->start();

QObject的moveToThread方法实现多线程

QObject的moveToThread方法可以将一个QObject对象移动到指定的线程中,实现多线程。

使用方式:

QObject *obj = new QObject();
QThread *thread = new QThread();
obj->moveToThread(thread);
thread->start();

示例:

** FileExportWorker.h **

#ifndef FILEEXPORTWORKER_H
#define FILEEXPORTWORKER_H #include <QObject>
#include "Utilities.h" class FileExportWorker : public QObject
{
Q_OBJECT
public:
explicit FileExportWorker(QObject *parent = nullptr);
void exportNumbers(int start, int end, const QString& prefix); signals:
void progressUpdated(int current, int total);
void statusUpdated(const QString& status); public slots:
}; #endif // FILEEXPORTWORKER_H

** FileExportWorker.cpp **

#include "FileExportWorker.h"
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDir>
#include <QDebug>
#include <QThread>
#include <QCoreApplication> FileExportWorker::FileExportWorker(QObject *parent)
: QObject(parent)
, m_start(1)
, m_end(10000)
, m_prefix("numbers")
, m_shouldStop(false)
{
} void FileExportWorker::setExportParams(int start, int end, const QString& prefix)
{
m_start = start;
m_end = end;
m_prefix = prefix;
} void FileExportWorker::doExport()
{
qDebug() << "Worker线程ID:" << QThread::currentThreadId();
qDebug() << "开始导出任务..."; m_shouldStop = false;
emit exportStarted();
emit statusUpdated("正在准备导出..."); try {
bool success = false; emit statusUpdated("使用自定义参数导出...");
success = exportNumbersWithProgress(); if (m_shouldStop) {
emit exportFinished(false, "导出已被用户取消");
} else if (success) {
emit exportFinished(true, QString("文件导出成功!范围:%1-%2").arg(m_start).arg(m_end));
} else {
emit exportFinished(false, "文件导出失败!");
} } catch (const std::exception& e) {
qDebug() << "导出过程中发生异常:" << e.what();
emit exportFinished(false, QString("导出过程中发生异常: %1").arg(e.what()));
} qDebug() << "导出任务完成";
} void FileExportWorker::stopExport()
{
m_shouldStop = true;
emit statusUpdated("正在停止导出...");
} bool FileExportWorker::exportNumbersWithProgress()
{
// 获取当前时间并格式化为文件名
QDateTime currentTime = QDateTime::currentDateTime();
QString timeString = currentTime.toString("yyyy-MM-dd_hh-mm-ss");
QString fileName = QString("%1_%2_to_%3_%4.txt")
.arg(m_prefix)
.arg(m_start)
.arg(m_end)
.arg(timeString); // 创建文件对象
QFile file(fileName); // 以写入模式打开文件
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "无法创建文件:" << fileName;
return false;
} // 创建文本流
QTextStream out(&file); int total = m_end - m_start + 1;
int count = 0; // 写入指定范围的数字
for (int i = m_start; i <= m_end; ++i) {
if (m_shouldStop) {
file.close();
QFile::remove(fileName); // 删除未完成的文件
return false;
} out << i;
count++; // 每10个数字换行
if (count % 10 == 0 || i == m_end) {
out << "\n";
} else {
out << " "; // 数字之间用空格分隔
} // 每处理100个数字发送一次进度更新
if (count % 100 == 0 || i == m_end) {
emit progressUpdated(count, total);
emit statusUpdated(QString("已处理 %1/%2 个数字").arg(count).arg(total));
// 让出CPU时间,允许其他操作
QCoreApplication::processEvents();
}
} // 关闭文件
file.close(); qDebug() << "成功写入文件:" << fileName;
qDebug() << "文件路径:" << QDir::currentPath() + "/" + fileName;
qDebug() << "写入数字范围:" << m_start << "到" << m_end << ",共" << total << "个数字"; return true;
}

QConcurrent实现多线程导出数据

QConcurrent是Qt提供的一个并发编程框架,用于简化多线程编程。它提供了一些方便的函数和类,用于在多个线程中执行任务。本示例通过QConcurrent实现导出任务,实现多线程导出数据。

使用方式:

QFuture<bool> future = QConcurrent::run(this, &FileExportWorker::exportNumbersWithProgress);

示例:

** FileExportWorker.h **

#ifndef CONCURRENTEXPORTER_H
#define CONCURRENTEXPORTER_H #include <QObject>
#include <QString>
#include <QFuture>
#include <QFutureWatcher>
#include <QtConcurrent>
#include "Utilities.h" class ConcurrentExporter : public QObject
{
Q_OBJECT public:
explicit ConcurrentExporter(QObject *parent = nullptr); // 开始导出任务
void startExport(int start = 1, int end = 10000, const QString& prefix = "concurrent"); // 取消导出任务
void cancelExport(); // 检查是否正在运行
bool isRunning() const; signals:
void exportStarted();
void exportFinished(bool success, const QString& message); private slots:
void onExportFinished(); private:
QFutureWatcher<bool> *m_watcher;
QFuture<bool> m_future;
int m_start;
int m_end;
QString m_prefix;
}; #endif // CONCURRENTEXPORTER_H

** FileExportWorker.cpp **

#include "ConcurrentExporter.h"
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDir>
#include <QDebug>
#include <QThread>
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrentRun> ConcurrentExporter::ConcurrentExporter(QObject *parent)
: QObject(parent)
, m_watcher(new QFutureWatcher<bool>(this))
, m_start(1)
, m_end(10000)
, m_prefix("concurrent")
{
// 连接QFutureWatcher的信号
connect(m_watcher, &QFutureWatcher<bool>::finished, this, &ConcurrentExporter::onExportFinished);
} void ConcurrentExporter::startExport(int start, int end, const QString& prefix)
{
if (isRunning()) {
qDebug() << "导出任务已在运行中";
return;
} m_start = start;
m_end = end;
m_prefix = prefix; qDebug() << "使用Qt Concurrent开始导出任务...";
qDebug() << "当前线程ID:" << QThread::currentThreadId(); emit exportStarted(); m_future = QtConcurrent::run([=]() {
qDebug() << "工作线程ID:" << QThread::currentThreadId();
return Utilities::writeNumbersToFile(start, end, prefix);
});
// 设置QFutureWatcher监视QFuture
m_watcher->setFuture(m_future);
} void ConcurrentExporter::cancelExport()
{
if (isRunning()) {
m_future.cancel();
}
} bool ConcurrentExporter::isRunning() const
{
return m_future.isRunning();
} void ConcurrentExporter::onExportFinished()
{
bool success = false;
QString message; if (m_future.isCanceled()) {
message = "导出任务已被取消";
} else {
try {
success = m_future.result();
if (success) {
message = QString("文件导出成功!范围:%1-%2").arg(m_start).arg(m_end);
} else {
message = "文件导出失败!";
}
} catch (const std::exception& e) {
message = QString("导出过程中发生异常: %1").arg(e.what());
}
} emit exportFinished(success, message);
qDebug() << "Qt Concurrent导出任务完成:" << message;
}

QRunnable结合QThreadPool方法实现多线程导出数据

QRunnable是Qt提供的一个接口,用于在多线程中执行任务。QThreadPool是一个线程池,用于管理多个线程。本示例通过QRunnable接口实现导出任务,通过QThreadPool线程池管理线程,实现多线程导出数据。

使用方式:

QThreadPool *pool = QThreadPool::globalInstance();
RunnableExportTask *task = new RunnableExportTask(1, 10000, "numbers");
pool->start(task);

示例:

** RunnableExportTask.h **

#ifndef RUNNABLEEXPORTTASK_H
#define RUNNABLEEXPORTTASK_H #include <QRunnable>
#include <QObject>
#include <QString>
#include <QDebug>
#include "Utilities.h" // 由于QRunnable不继承QObject,我们需要一个信号发射器
class ExportTaskNotifier : public QObject
{
Q_OBJECT public:
explicit ExportTaskNotifier(QObject *parent = nullptr) : QObject(parent) {} void emitStarted() { emit exportStarted(); }
void emitFinished(bool success, const QString& message) { emit exportFinished(success, message); }
void emitProgress(const QString& status) { emit progressUpdated(status); } signals:
void exportStarted();
void exportFinished(bool success, const QString& message);
void progressUpdated(const QString& status);
}; class RunnableExportTask : public QRunnable
{
public:
explicit RunnableExportTask(int start = 1, int end = 10000, const QString& prefix = "runnable"); // 设置通知器,用于发送信号
void setNotifier(ExportTaskNotifier *notifier); // 设置导出参数
void setExportParams(int start, int end, const QString& prefix); // QRunnable接口实现
void run() override; private:
int m_start;
int m_end;
QString m_prefix;
ExportTaskNotifier *m_notifier;
}; #endif // RUNNABLEEXPORTTASK_H

RunnableExportTask.cpp

#include "RunnableExportTask.h"
#include <QThread>
#include <QDebug> RunnableExportTask::RunnableExportTask(int start, int end, const QString& prefix)
: m_start(start)
, m_end(end)
, m_prefix(prefix)
, m_notifier(nullptr)
{
// 设置任务完成后自动删除
setAutoDelete(true);
} void RunnableExportTask::setNotifier(ExportTaskNotifier *notifier)
{
m_notifier = notifier;
} void RunnableExportTask::setExportParams(int start, int end, const QString& prefix)
{
m_start = start;
m_end = end;
m_prefix = prefix;
} void RunnableExportTask::run()
{
qDebug() << "QRunnable任务开始运行...";
qDebug() << "当前线程ID:" << QThread::currentThreadId(); if (m_notifier) {
m_notifier->emitStarted();
m_notifier->emitProgress("QRunnable任务:正在准备导出...");
} try {
if (m_notifier) {
m_notifier->emitProgress("QRunnable任务:开始写入文件...");
} bool success = Utilities::writeNumbersToFile(m_start, m_end, m_prefix); if (success) {
QString message = QString("文件导出成功!范围:%1-%2").arg(m_start).arg(m_end);
if (m_notifier) {
m_notifier->emitProgress("QRunnable任务:导出完成");
m_notifier->emitFinished(true, message);
}
qDebug() << "QRunnable任务完成:" << message;
} else {
QString message = "文件导出失败!";
if (m_notifier) {
m_notifier->emitFinished(false, message);
}
qDebug() << "QRunnable任务失败:" << message;
} } catch (const std::exception& e) {
QString message = QString("导出过程中发生异常: %1").arg(e.what());
qDebug() << "QRunnable任务异常:" << message;
if (m_notifier) {
m_notifier->emitFinished(false, message);
}
} qDebug() << "QRunnable任务结束";
}

Qt | 四种方式实现多线程导出数据功能的更多相关文章

  1. Java使用基本字节流OutputStream的四种方式对于数据复制(文本,音视频,图像等数据)

    //package 字符缓冲流bufferreaderDemo; import java.io.BufferedOutputStream; import java.io.FileInputStream ...

  2. 实现web数据同步的四种方式

    http://www.admin10000.com/document/6067.html 实现web数据同步的四种方式 1.nfs实现web数据共享 2.rsync +inotify实现web数据同步 ...

  3. 【Linux】多线程同步的四种方式

    背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题? 通过多线程模拟多窗口售票为例: #include <iostream> #include<pthread.h> ...

  4. linux下实现web数据同步的四种方式(性能比较)

    实现web数据同步的四种方式 ======================================= 1.nfs实现web数据共享2.rsync +inotify实现web数据同步3.rsyn ...

  5. C#批量插入数据到Sqlserver中的四种方式

    我的新书ASP.NET MVC企业级实战预计明年2月份出版,感谢大家关注! 本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的 ...

  6. C#_批量插入数据到Sqlserver中的四种方式

    先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生成一个GUID算法所花的时间肯定比你从数据表中重新查询上一条记 ...

  7. (转)四种常见的 POST 提交数据方式

    四种常见的 POST 提交数据方式(转自:https://imququ.com/post/four-ways-to-post-data-in-http.html) HTTP/1.1 协议规定的 HTT ...

  8. .NET MVC控制器向视图传递数据的四种方式

    .NET MVC控制器向视图传递数据的四种方式: 1.ViewBag  ViewBag.Mvc="mvc"; 2.ViewData ViewBag["Mvc"] ...

  9. C#批量插入数据到Sqlserver中的四种方式 - 转

    先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生成一个GUID算法所花的时间肯定比你从数据表中重新查询上一条记 ...

  10. 四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml)

    四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml) 转 ...

随机推荐

  1. [安洵杯 2019]easy_serialize_php 1 WP

    [安洵杯 2019]easy_serialize_php 1 WP 这道题目考察的主要是序列化与反序列化过程中,对象逃逸的一个漏洞. 说是对象逃逸,我觉得可能叫对象注入比较形象. 首先题目上来可以看到 ...

  2. SharpIco:用纯C#打造零依赖的.ico图标生成器,支持.NET9与AOT编译

    前言 最近一直在完善我今年的两款桌面软件:视频剪辑工具 Clipify 和 AI 文章创作工具 StarBlogPublisher 虽然界面是基本完善了,但图标还是默认的,显得很不专业 于是我打算给这 ...

  3. 互联网的前世今生:Web 1.0、2.0、3.0

    101链视界:区块链.隐私计算与金融科技领域的专业知识分享者. ▲ 点击上方第二个"101链视界"关注公众号 2021年,元宇宙成为一个爆火的概念,而Web 3.0也悄悄在海外科技 ...

  4. Java中的静态块(static{})

    静态块(static{}) (1) static关键字还有一个比较关键的作用,用来形成静态代码块(static{} 即static块 )以优化程序性能. (2) static块可以置于类中的任何地方, ...

  5. 我们不可能永远都在救火 ——Scrum中技术债务“偿还”指南

    技术债务是指开发人员为了加速软件开发,在应该采用最佳方案时进行了妥协,改用了短期内能加速软件开发的方案,以至于未来给自己带来额外的开发负担. 软件工程师 Ward Cunningham首次将技术的复杂 ...

  6. 不写一行代码 .NET 使用 FluentCMS 快速构建现代化内容管理系统(CMS)

    前言 在当今数字化时代,内容管理系统(CMS)已成为企业和个人构建网站.博客.电子商务平台等数字应用不可或缺的工具.随着技术的不断发展,现代CMS不仅要求具备强大的内容管理功能,还需要具备高度的灵活性 ...

  7. java代码运行出现DENIED Redis is running in protected mode because protected mode is enabled 问题解决

    这个错误是因为开启了保护模式,导致出错.所以需要关闭redis的保护模式. 编辑redis的redis.config 注释 bind 127.0.0.1  .修改protected-mode 为 no ...

  8. HDFS常用命令操作

    HDFS常用操作命令如下: 查询目录 # 查看/目录下的目录和文件 hadoop fs -ls / # 递归查看/目录下的目录和文件 hadoop fs -lsr / 创建目录 # 新增目录/user ...

  9. MySQL查询优化的步骤

    本文由 ChatMoney团队出品 MySQL查询优化是提高数据库性能的关键步骤之一.通过优化查询,可以减少数据库的负载,提高查询速度,从而提高整个应用程序的性能. 选择合适的索引 索引是数据库中用于 ...

  10. DRF之Response源码分析

    DRF之Response源码分析 [一]响应类的对象Response源码 [1]路由 from django.contrib import admin from django.urls import ...