耘者 發表於 2025-8-6 10:02:00

Qt | 四种方式实现多线程导出数据功能

<h2 id="前言">前言</h2>
<p>在以往的项目开发中,在很多地方用到了多线程。针对不同的业务逻辑,需要使用不同的多线程实现方法,来达到优化项目的目的。本文记录下在Qt开发中用到的多线程技术实现方法,以导出指定范围的数字到txt文件为例,展示多线程不同的实现方式。<br>
示例已上传到gittee,地址:https://gitee.com/zbylalalala1/qt_-thread-demo.git</p>
<p><img src="https://img2024.cnblogs.com/blog/2418664/202508/2418664-20250806095931500-1570154158.png"></p>
<h2 id="导出文件的示例工具类">导出文件的示例工具类</h2>
<p>首先提供一个工具类,用于将指定范围的数字写入txt文件。</p>
<pre><code>#ifndef UTILITIES_H
#define UTILITIES_H

#include &lt;QString&gt;
#include &lt;QFile&gt;
#include &lt;QTextStream&gt;
#include &lt;QDateTime&gt;
#include &lt;QDir&gt;
#include &lt;QDebug&gt;
class Utilities
{
public:
    static bool writeNumbersToFile(int start, int end, const QString&amp; prefix = "numbers")
    {
      if (start &gt; end) {
            qDebug() &lt;&lt; "起始数字不能大于结束数字";
            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() &lt;&lt; "无法创建文件:" &lt;&lt; fileName;
            return false;
      }
      
      // 创建文本流
      QTextStream out(&amp;file);
      
      // 写入指定范围的数字
      int count = 0;
      for (int i = start; i &lt;= end; ++i) {
            out &lt;&lt; i;
            count++;
            
            // 每10个数字换行
            if (count % 10 == 0 || i == end) {
                out &lt;&lt; "\n";
            } else {
                out &lt;&lt; " "; // 数字之间用空格分隔
            }
      }
      
      // 关闭文件
      file.close();
      
      qDebug() &lt;&lt; "成功写入文件:" &lt;&lt; fileName;
      qDebug() &lt;&lt; "文件路径:" &lt;&lt; QDir::currentPath() + "/" + fileName;
      qDebug() &lt;&lt; "写入数字范围:" &lt;&lt; start &lt;&lt; "到" &lt;&lt; end &lt;&lt; ",共" &lt;&lt; (end - start + 1) &lt;&lt; "个数字";
      
      return true;
    }
   
    // 获取当前工作目录
    static QString getCurrentPath()
    {
      return QDir::currentPath();
    }
   
    // 检查文件是否存在
    static bool fileExists(const QString&amp; fileName)
    {
      QFile file(fileName);
      return file.exists();
    }
};

#endif // UTILITIES_H

</code></pre>
<h2 id="qthread">QThread</h2>
<p>使用QThread类来创建线程,是Qt中最简单的一种多线程实现方式,不过一般不建议使用,因为它的功能比较有限。<br>
使用QThread的方式为:继承QThread并重写run()函数。<br>
** ExportThread.h **</p>
<pre><code>#ifndef EXPORTTHREAD_H
#define EXPORTTHREAD_H

#include &lt;QThread&gt;
#include &lt;QDebug&gt;
#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&amp; prefix = "numbers");
   
protected:
    void run() override;
   
signals:
    void exportStarted();
    void exportFinished(bool success, const QString&amp; message);
    void progressUpdate(int current, int total);
   
private:
    int m_start;
    int m_end;
    QString m_prefix;
};

#endif // EXPORTTHREAD_H
</code></pre>
<p>** ExportThread.cpp **</p>
<pre><code>#include "ExportThread.h"
#include &lt;QDateTime&gt;
#include &lt;QDir&gt;

ExportThread::ExportThread(QObject *parent)
    : QThread(parent)
    , m_start(1)
    , m_end(10000)
    , m_prefix("numbers")
{
}

void ExportThread::setExportParams(int start, int end, const QString&amp; prefix)
{
    m_start = start;
    m_end = end;
    m_prefix = prefix;
}

void ExportThread::run()
{
    qDebug() &lt;&lt; "导出线程开始运行...";
    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() &lt;&lt; "导出线程完成";
      
    } catch (const std::exception&amp; e) {
      qDebug() &lt;&lt; "导出过程中发生异常:" &lt;&lt; e.what();
      emit exportFinished(false, QString("导出过程中发生异常: %1").arg(e.what()));
    }
}
</code></pre>
<p>使用方式:</p>
<pre><code>ExportThread *exportThread = new ExportThread(this);
exportThread-&gt;setExportParams(1, 10000, "numbers");
exportThread-&gt;start();
</code></pre>
<h2 id="qobject的movetothread方法实现多线程">QObject的moveToThread方法实现多线程</h2>
<p>QObject的moveToThread方法可以将一个QObject对象移动到指定的线程中,实现多线程。<br>
使用方式:</p>
<pre><code>QObject *obj = new QObject();
QThread *thread = new QThread();
obj-&gt;moveToThread(thread);
thread-&gt;start();
</code></pre>
<p>示例:<br>
** FileExportWorker.h **</p>
<pre><code>#ifndef FILEEXPORTWORKER_H
#define FILEEXPORTWORKER_H

#include &lt;QObject&gt;
#include "Utilities.h"

class FileExportWorker : public QObject
{
    Q_OBJECT
public:
    explicit FileExportWorker(QObject *parent = nullptr);
    void exportNumbers(int start, int end, const QString&amp; prefix);

signals:
    void progressUpdated(int current, int total);
    void statusUpdated(const QString&amp; status);

public slots:
};

#endif // FILEEXPORTWORKER_H
</code></pre>
<p>** FileExportWorker.cpp **</p>
<pre><code>#include "FileExportWorker.h"
#include &lt;QFile&gt;
#include &lt;QTextStream&gt;
#include &lt;QDateTime&gt;
#include &lt;QDir&gt;
#include &lt;QDebug&gt;
#include &lt;QThread&gt;
#include &lt;QCoreApplication&gt;

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&amp; prefix)
{
    m_start = start;
    m_end = end;
    m_prefix = prefix;
}

void FileExportWorker::doExport()
{
    qDebug() &lt;&lt; "Worker线程ID:" &lt;&lt; QThread::currentThreadId();
    qDebug() &lt;&lt; "开始导出任务...";
   
    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&amp; e) {
      qDebug() &lt;&lt; "导出过程中发生异常:" &lt;&lt; e.what();
      emit exportFinished(false, QString("导出过程中发生异常: %1").arg(e.what()));
    }
   
    qDebug() &lt;&lt; "导出任务完成";
}

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() &lt;&lt; "无法创建文件:" &lt;&lt; fileName;
      return false;
    }
   
    // 创建文本流
    QTextStream out(&amp;file);
   
    int total = m_end - m_start + 1;
    int count = 0;
   
    // 写入指定范围的数字
    for (int i = m_start; i &lt;= m_end; ++i) {
      if (m_shouldStop) {
            file.close();
            QFile::remove(fileName); // 删除未完成的文件
            return false;
      }
      
      out &lt;&lt; i;
      count++;
      
      // 每10个数字换行
      if (count % 10 == 0 || i == m_end) {
            out &lt;&lt; "\n";
      } else {
            out &lt;&lt; " "; // 数字之间用空格分隔
      }
      
      // 每处理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() &lt;&lt; "成功写入文件:" &lt;&lt; fileName;
    qDebug() &lt;&lt; "文件路径:" &lt;&lt; QDir::currentPath() + "/" + fileName;
    qDebug() &lt;&lt; "写入数字范围:" &lt;&lt; m_start &lt;&lt; "到" &lt;&lt; m_end &lt;&lt; ",共" &lt;&lt; total &lt;&lt; "个数字";
   
    return true;
}
</code></pre>
<h2 id="qconcurrent实现多线程导出数据">QConcurrent实现多线程导出数据</h2>
<p>QConcurrent是Qt提供的一个并发编程框架,用于简化多线程编程。它提供了一些方便的函数和类,用于在多个线程中执行任务。本示例通过QConcurrent实现导出任务,实现多线程导出数据。</p>
<p>使用方式:</p>
<pre><code>QFuture&lt;bool&gt; future = QConcurrent::run(this, &amp;FileExportWorker::exportNumbersWithProgress);
</code></pre>
<p>示例:<br>
** FileExportWorker.h **</p>
<pre><code>#ifndef CONCURRENTEXPORTER_H
#define CONCURRENTEXPORTER_H

#include &lt;QObject&gt;
#include &lt;QString&gt;
#include &lt;QFuture&gt;
#include &lt;QFutureWatcher&gt;
#include &lt;QtConcurrent&gt;
#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&amp; prefix = "concurrent");
   
    // 取消导出任务
    void cancelExport();
   
    // 检查是否正在运行
    bool isRunning() const;

signals:
    void exportStarted();
    void exportFinished(bool success, const QString&amp; message);

private slots:
    void onExportFinished();

private:
    QFutureWatcher&lt;bool&gt; *m_watcher;
    QFuture&lt;bool&gt; m_future;
    int m_start;
    int m_end;
    QString m_prefix;
};

#endif // CONCURRENTEXPORTER_H

</code></pre>
<p>** FileExportWorker.cpp **</p>
<pre><code>#include "ConcurrentExporter.h"
#include &lt;QFile&gt;
#include &lt;QTextStream&gt;
#include &lt;QDateTime&gt;
#include &lt;QDir&gt;
#include &lt;QDebug&gt;
#include &lt;QThread&gt;
#include &lt;QCoreApplication&gt;
#include &lt;QtConcurrent/QtConcurrentRun&gt;

ConcurrentExporter::ConcurrentExporter(QObject *parent)
    : QObject(parent)
    , m_watcher(new QFutureWatcher&lt;bool&gt;(this))
    , m_start(1)
    , m_end(10000)
    , m_prefix("concurrent")
{
    // 连接QFutureWatcher的信号
    connect(m_watcher, &amp;QFutureWatcher&lt;bool&gt;::finished, this, &amp;ConcurrentExporter::onExportFinished);
}

void ConcurrentExporter::startExport(int start, int end, const QString&amp; prefix)
{
    if (isRunning()) {
      qDebug() &lt;&lt; "导出任务已在运行中";
      return;
    }
   
    m_start = start;
    m_end = end;
    m_prefix = prefix;
   
    qDebug() &lt;&lt; "使用Qt Concurrent开始导出任务...";
    qDebug() &lt;&lt; "当前线程ID:" &lt;&lt; QThread::currentThreadId();
   
    emit exportStarted();
   
    m_future = QtConcurrent::run([=]() {
      qDebug() &lt;&lt; "工作线程ID:" &lt;&lt; QThread::currentThreadId();
      return Utilities::writeNumbersToFile(start, end, prefix);
    });
    // 设置QFutureWatcher监视QFuture
    m_watcher-&gt;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&amp; e) {
            message = QString("导出过程中发生异常: %1").arg(e.what());
      }
    }
   
    emit exportFinished(success, message);
    qDebug() &lt;&lt; "Qt Concurrent导出任务完成:" &lt;&lt; message;
}
</code></pre>
<h2 id="qrunnable结合qthreadpool方法实现多线程导出数据">QRunnable结合QThreadPool方法实现多线程导出数据</h2>
<p>QRunnable是Qt提供的一个接口,用于在多线程中执行任务。QThreadPool是一个线程池,用于管理多个线程。本示例通过QRunnable接口实现导出任务,通过QThreadPool线程池管理线程,实现多线程导出数据。</p>
<p>使用方式:</p>
<pre><code>QThreadPool *pool = QThreadPool::globalInstance();
RunnableExportTask *task = new RunnableExportTask(1, 10000, "numbers");
pool-&gt;start(task);
</code></pre>
<p>示例:<br>
** RunnableExportTask.h **</p>
<pre><code>#ifndef RUNNABLEEXPORTTASK_H
#define RUNNABLEEXPORTTASK_H

#include &lt;QRunnable&gt;
#include &lt;QObject&gt;
#include &lt;QString&gt;
#include &lt;QDebug&gt;
#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&amp; message) { emit exportFinished(success, message); }
    void emitProgress(const QString&amp; status) { emit progressUpdated(status); }

signals:
    void exportStarted();
    void exportFinished(bool success, const QString&amp; message);
    void progressUpdated(const QString&amp; status);
};

class RunnableExportTask : public QRunnable
{
public:
    explicit RunnableExportTask(int start = 1, int end = 10000, const QString&amp; prefix = "runnable");
   
    // 设置通知器,用于发送信号
    void setNotifier(ExportTaskNotifier *notifier);
   
    // 设置导出参数
    void setExportParams(int start, int end, const QString&amp; prefix);
   
    // QRunnable接口实现
    void run() override;
   
private:
    int m_start;
    int m_end;
    QString m_prefix;
    ExportTaskNotifier *m_notifier;
};

#endif // RUNNABLEEXPORTTASK_H
</code></pre>
<p>RunnableExportTask.cpp</p>
<pre><code>#include "RunnableExportTask.h"
#include &lt;QThread&gt;
#include &lt;QDebug&gt;

RunnableExportTask::RunnableExportTask(int start, int end, const QString&amp; 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&amp; prefix)
{
    m_start = start;
    m_end = end;
    m_prefix = prefix;
}

void RunnableExportTask::run()
{
    qDebug() &lt;&lt; "QRunnable任务开始运行...";
    qDebug() &lt;&lt; "当前线程ID:" &lt;&lt; QThread::currentThreadId();
   
    if (m_notifier) {
      m_notifier-&gt;emitStarted();
      m_notifier-&gt;emitProgress("QRunnable任务:正在准备导出...");
    }
   
    try {
      if (m_notifier) {
            m_notifier-&gt;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-&gt;emitProgress("QRunnable任务:导出完成");
                m_notifier-&gt;emitFinished(true, message);
            }
            qDebug() &lt;&lt; "QRunnable任务完成:" &lt;&lt; message;
      } else {
            QString message = "文件导出失败!";
            if (m_notifier) {
                m_notifier-&gt;emitFinished(false, message);
            }
            qDebug() &lt;&lt; "QRunnable任务失败:" &lt;&lt; message;
      }
      
    } catch (const std::exception&amp; e) {
      QString message = QString("导出过程中发生异常: %1").arg(e.what());
      qDebug() &lt;&lt; "QRunnable任务异常:" &lt;&lt; message;
      if (m_notifier) {
            m_notifier-&gt;emitFinished(false, message);
      }
    }
   
    qDebug() &lt;&lt; "QRunnable任务结束";
}
</code></pre><br><br>
来源:https://www.cnblogs.com/zbycode/p/19024524
頁: [1]
查看完整版本: Qt | 四种方式实现多线程导出数据功能