前言

在以往的项目开发中,在很多地方用到了多线程。针对不同的业务逻辑,需要使用不同的多线程实现方法,来达到优化项目的目的。本文记录下在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. unbuntu离线部署K8S集群

    环境准备和服务器规划部署前提已知条件: 环境准备与服务器规划总表 类别 配置项 详细信息 操作系统 版本 Ubuntu 25.04(所有节点) 容器运行时 Docker 版本 Docker 24.0. ...

  2. RabbitMQ单机&可能遇到的问题

    1.Deployments 1 kind: Deployment 2 apiVersion: apps/v1 3 metadata: 4 name: rabbitmq-deployment 5 spe ...

  3. 【转载】DeltaFIFO源码分析

    DeltaFIFO源码分析 介绍 我们已经知道 Reflector 中通过 ListAndWatch 获取到数据后传入到了本地的存储中,也就是 DeltaFIFO 中.从 DeltaFIFO 的名字可 ...

  4. Redis集群的三种姿势

    一.Redis主从复制 主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主. 1.如何实现 新建三个配置文件,分别命名为redis_ ...

  5. PI发布rest,json接口

    PI接口的开发分成两个部分,第一个部分是ESB(Enterprise Services Builder)部分,这里注意做数据结构定义,接口导入,字段关系映射,定义接口等. 第二部分是IB(Integr ...

  6. USB Gadget设备枚举失败的处理方法

    技术背景 我们的板子作为 USB Gadget 设备通过 USB 线接入 USB 主机使用,我们的板子被主机识别为一个 Compsite Device,这个 Compsite Device 是由我们板 ...

  7. Potree 004 点云点大小形状设置

    点云数据就是靠海量的点显示来模拟真实世界的.点大小设置就比较重要,例如如果数据稀疏,点显示的时候,可以设置稍微大一些.如果点数据比较密集,则可以显示小一些. 在Potree中,点云定义为PointCl ...

  8. C# WinForm 窗体阴影

    https://www.cnblogs.com/mqxs/p/9466218.html public class FormShadow : Form { public FormShadow() { I ...

  9. 可编程Modbus网关在非标称重仪表的应用

    1.概述 上海卓岚信息科技有限公司是一家专业提供工业物联网解决方案的高新技术企业,注册商标"ZLAN".公司研发的产品:物联网芯片.串口转以太网模块.串口服务器.可编程Modbus ...

  10. android多活动练习--人品计算器

    效果图如下: 第二个页面: 显示结果和姓名.性别有关,代码如下: activity_main.xml: 1 <?xml version="1.0" encoding=&quo ...