Qt多线程-QFuture 和 QFutureWatcher 的使用

前言

        在软件开发中,经常会遇到需要异步执行一系列耗时步骤的场景。当前一个操作失败时,我们希望停止后续操作;而如果成功,我们则继续执行下一步。这种情况常见于编译、构建等操作。比如编译Qt项目需要执行qmake和make两个步骤,如果在执行qmake失败之后就不执行make了,如果qmake执行成功则执行make操作,下面以这个示例来分析代码。

详细实现

        QFuture类表示异步计算的结果。而QFutureWatcher类允许使用信号和插槽监视QFuture。BuildStepManager中使用QFutureWatcher来监控每个步骤的QFuture。

BuildStep类

        BuildStep类是步骤的基类,实现了命令的执行、命令日志的输入等。run方法传递了一个QFutureInterface<bool>的变量用于报告执行成功与否。QMake和Make两个类都是重BuildStep继承而来的。

#include "buildstep.h"
#include <QDir>
#include <QProcess>
#include <QDebug>


BuildStep::BuildStep(QObject *parent) : QObject(parent)
{
    connect(this, &BuildStep::output, this, &BuildStep::logger);
}


void BuildStep::reportRunResult(QFutureInterface<bool> &fi, bool success)
{
    fi.reportResult(success);
    fi.reportFinished();
}

void BuildStep::run(QFutureInterface<bool> &fi)
{
    if (!checkWorkingDir()) {
        reportRunResult(fi, false);
        return;
    }

    // 创建命令设置工作目录、环境变量、参数等。
    m_process.reset(new QProcess);
    m_process->setProgram(m_param.command());
    m_process->setWorkingDirectory(m_param.workingDirectory());
    m_process->setArguments(m_param.arguments());
    m_process->setEnvironment(m_param.environment());

    // 连接退出、打印等信号
    connect(m_process.data(), static_cast<void (QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished),
            this, &BuildStep::slotProcessFinished);
    connect(m_process.data(), &QProcess::readyReadStandardOutput, this, &BuildStep::processReadyReadStdOutput);

    connect(m_process.data(), &QProcess::readyReadStandardError, this, &BuildStep::processReadyReadStdError);

    m_futureInterface = &fi;
    emit output(tr("Starting: \"%1\" %2").arg(QDir::toNativeSeparators(m_process->program())).arg(m_process->arguments().join(" ")), 1);
    m_process->start();
    if (!m_process->waitForFinished()) {
        emit output(tr("Could not start process \"%1\" %2").arg(QDir::toNativeSeparators(m_process->program())).arg(m_process->arguments().join(" ")), 2);
        reportRunResult(fi, false);
        return;
    }
}

bool BuildStep::checkWorkingDir()
{
    // 创建工作目录
    QString directory = m_param.workingDirectory();
    QDir dir(directory);
    if (!dir.exists()) {
        bool result = dir.mkpath(directory);
        if (!result) {
            emit output(tr("create dir %1 failed.").arg(directory), 2);
            return false;
        }
    }
    return true;
}

void BuildStep::slotProcessFinished(int, QProcess::ExitStatus)
{
    QString command = QDir::toNativeSeparators(m_process->program());
    int exitCode = m_process->exitCode();
    QProcess::ExitStatus status =  m_process->exitStatus();
    if (status == QProcess::NormalExit && exitCode == 0) {
        emit output(tr("The process \"%1\" exited normally.").arg(command), 1);
    } else if (status == QProcess::NormalExit) {
        emit output(tr("The process \"%1\" exited with code %2.").arg(command, QString::number(exitCode)), 2);
    } else {
        emit output(tr("The process \"%1\" crashed.").arg(command), 2);
    }

    const bool returnValue = (exitCode == 0 && status == QProcess::NormalExit);

    reportRunResult(*m_futureInterface, returnValue);
    m_process.reset();
}

void BuildStep::processReadyReadStdOutput()
{
    if (m_process.isNull())
        return;
    m_process->setReadChannel(QProcess::StandardOutput);

    QString message;
    while (m_process->canReadLine()) {
        QString line = QString::fromUtf8(m_process->readLine());
        message += line;

    }
    emit output(message);
}

void BuildStep::processReadyReadStdError()
{
    if (m_process.isNull())
        return;
    m_process->setReadChannel(QProcess::StandardError);
    QString message;
    while (m_process->canReadLine()) {
        QString line = QString::fromUtf8(m_process->readLine());
        message += line;

    }
    emit output(message, 2);
}

void BuildStep::logger(const QString &message, int type)
{
    if (type == 0) {
        qDebug() << qPrintable(message);
    } else if (type == 1) {
        qInfo() << qPrintable(message);
    } else if (type == 2) {
        qCritical() << qPrintable(message);
    }
}

Job类

        Job从 QRunnable 派生,并封装了一个构建步骤。它使用 QFutureInterface<bool> 来管理异步执行结果。run 方法被实现用于执行构建步骤的逻辑。成功时,通过 m_futureInterface.reportFinished(true) 报告任务完成;失败时,通过 m_futureInterface.reportFinished(false) 报告任务取消。

class Job : public QRunnable
{
public:
    Job(BuildStep *bs) : m_buildStep(bs)
    {
        m_futureInterface.setRunnable(this);
        m_futureInterface.reportStarted();
    }

    ~Job() override
    {
        m_futureInterface.reportFinished();
    }

    QFuture<bool> future() { return m_futureInterface.future(); }

    void run() override
    {
        if (m_futureInterface.isCanceled()) {
            m_futureInterface.reportFinished();
            return;
        }
        if (m_buildStep != nullptr) {
            m_buildStep->run(m_futureInterface);
        }
    }

private:
    BuildStep *m_buildStep;
    QFutureInterface<bool> m_futureInterface;
};

BuildStepManager 类

        BuildStepManager管理监控整个流程。addStep 方法将构建步骤添加到管理器。start 方法启动构建过程。nextBuild 槽由 QFutureWatcher 在任务完成时触发。它确定前一个步骤是否成功,是否执行下一个步骤。

BuildStepManager::BuildStepManager(QObject *parent) : QObject(parent), m_currentStep(nullptr)
{
    qRegisterMetaType<QProcess::ExitStatus>();
    connect(&m_watcher, &QFutureWatcherBase::finished,
            this, &BuildStepManager::nextBuild, Qt::QueuedConnection);
    m_timer.reset(new QElapsedTimer);
}

BuildStepManager::~BuildStepManager()
{

}

void BuildStepManager::addStep(BuildStep *bs)
{
    if (bs != nullptr)
        m_buildSteps << bs;
}

void BuildStepManager::start()
{
    m_timer->start();
    nextStep();
}

static QString formatElapsedTime(qint64 elapsed)
{
    elapsed += 500;
    const QString format = QString::fromLatin1(elapsed >= 3600000 ? "h:mm:ss" : "mm:ss");
    const QString time = QTime(0, 0).addMSecs(elapsed).toString(format);
    return QObject::tr("Elapsed time: %1.").arg(time);
}

void BuildStepManager::nextBuild()
{
    if (m_currentStep != nullptr)
        delete m_currentStep;
    const bool success = m_watcher.result();
    if (success) {
        // 前一个步骤成功完成
        nextStep();
    } else {
        // 如果失败了,删除后面的步骤
        qDeleteAll(m_buildSteps);
        qInfo() << formatElapsedTime(m_timer->elapsed());
    }
}

void BuildStepManager::nextStep()
{
    if (!m_buildSteps.empty()) {
        m_currentStep = m_buildSteps.takeFirst();
        auto job = new Job(m_currentStep);
        job->setAutoDelete(false);
        m_watcher.setFuture(job->future());
        QThreadPool::globalInstance()->start(job);
    } else {
        qInfo() << formatElapsedTime(m_timer->elapsed());
    }
}

QMake类

        QMake类用于执行qmake命令。

#include "qmake.h"

#include <QDir>

QMake::QMake(const QString &file, const QString &workingDirectory)
    : m_file(file)
    , m_directory(workingDirectory)
{
}

void QMake::setQmake(const QString &qmake)
{
    m_qmake = qmake;
}

void QMake::run(QFutureInterface<bool> &fi)
{
    if (m_qmake.isEmpty()) {
        emit output(tr("Please set valid qt path."), 1);
        reportRunResult(fi, false);
        return;
    }


    ProcessParameters *p = processParameters();
    QStringList env = QProcess::systemEnvironment();
    // Todo 添加环境变量
    p->setEnvironment(env);
    p->setCommand(m_qmake);
    p->setWorkingDirectory(m_directory);
    p->setArguments(QStringList()  << QDir::toNativeSeparators(m_file) << "-r" << "-spec" << "win32-g++" << "CONFIG+=release");
    BuildStep::run(fi);
}

Make类

        Make类用于执行make命令。

#include "make.h"

Make::Make(const QString &directory)
    : m_directory(directory)
{

}

void Make::setMake(const QString &make)
{
    m_make = make;
}

void Make::run(QFutureInterface<bool> &fi)
{
    if (m_make.isEmpty()) {
        emit output(tr("Please set valid make path."), 1);
        reportRunResult(fi, false);
        return;
    }

    ProcessParameters *p = processParameters();

    p->setWorkingDirectory(m_directory);
    p->setCommand(m_make);
    BuildStep::run(fi);
}

测试

        在main.cpp中添加代码。注意路径根据自己的情况修改。

#include <QCoreApplication>
#include "buildstepmanager.h"
#include "make.h"
#include "qmake.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    BuildStepManager manager;

    QMake *qmake = new QMake("D:/Work/TestProject/QFutureDemo/QFutureDemo.pro", "D:/Build/QFutureDemo");
    qmake->setQmake("C:/Qt/Qt5.9.8/5.9.8/mingw53_32/bin/qmake.exe");
    manager.addStep(qmake);
    Make *make = new Make("D:/Build/QFutureDemo");
    make->setMake("C:/DevelpoerTools/Tools/mingw530_32/bin/mingw32-make.exe");
    manager.addStep(make);
    manager.start();
    return a.exec();
}

        编译完成执行结果:

        示例代码:QFutureDemo

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
2022年11月4日-2022年11月14日购买当前课程赠送课程学习地址如下:https://edu.csdn.net/course/detail/32434https://edu.csdn.net/course/detail/35658https://edu.csdn.net/course/detail/30223https://edu.csdn.net/course/detail/32408https://edu.csdn.net/course/detail/32429注:因赠送课程不会出现在已订阅课程列表中,以下课程学习地址一定要收藏保存。#课程服务 在线答疑:本课程设有专门的讨论留言区,学习中遇到任何问题,直接给老师留言即可,老师都会及时进行回复。远程协助:如果遇到复杂问题,老师还可进行远程协助,这个一般可不是一两百元的课程就能享受到的。源码分享:为了让大家更好的进行项目实战,老师还将课程中涉及到的所有源码分享给学员,按照视频中的提示进行下载即可。在CSDN分享C++ Qt开发知识已经有6年了,感谢众多博友对我的支持,了解到很多人对Qt使用还是有些困扰,例如Qt环境搭建,Qt布局的使用,如何使用Qt编写复杂的界面,如何自定义非标控件,Qt如何和Web交互,Qt和后台接口如何交互等;经过这几年的整理,我决定出这套《Qt高级开发视频教程》,带领大家学习Qt高级开发知识,学习如何使用Qt开发企业级别的项目;通过本课程的学习,大家将会达到企业招聘的中高级要求。为了照顾零基础学员,本课程第一章会介绍Qt环境搭建、QtCreator / VS2019的基本使用方法,Qt整体架构、Qt信号机制,Qt内存管理等知识。即使没有Qt开发的学习经验,也能跟着课程顺利学习。课程核心知识点地图如下: 课程每章核心知识点介绍如下: 第一章:介绍Qt环境搭建、QtCreator / VS2019的基本使用方法,Qt整体架构、Qt信号机制,Qt内存管理等知识。第二章:了解到很多学员对于Qt界面布局很不熟悉,将会详细介绍Qt设计器布局,以及如何C++代码手写布局,从常见的企业级项目入手,带领大家学会各种布局的实现,例如WPS、腾讯会议、优酷、迅雷等界面的实现;界面布局会了,这是企业项目开发的第一步,还有更重要的无边框窗口,如何设计一个合理的无边框窗口很重要,第三/四章:详细介绍如何实现一个无边框窗口,如何自定义标题栏,如何实现拖拽拉伸;第四章将会介绍如何自定义非标控件,优化Qt界面。第五章:介绍Qt web混合编程,一个商用项目,必然会涉及到web交互,这也是很多Qt开发者的弱项,这一章讲详细介绍C++ Qt web混合开发。第六章:既然是做企业级项目,必然需要和后台交互,http编程也是必要的,将详细介绍http编程,用户注册,登录,后台接口请求等知识;通过第五、六章的学习,将会是你的Qt开发技术更上一层楼。第七章:介绍Qt并发编程,耗时任务处理,进程调用等知识。第八、九章:讲解 Qt 比较重要的知识,图形视图结构,以及MVD模式;通过这两章的学习,大家会对图形视图有更好的了解。第十章:本章是独立章节,主要介绍Qt中一些特殊技巧,项目编译,dpi适配、多语言等知识。第十一章:是我们的企业级项目实战:实现一个视频会议客户端,本项目可以进行多人视频通话,直播,桌面分享等功能,本项目我会从零开始,进行项目搭建,功能调试,bug fixed, 带领大家做一个企业级项目。希望通过本课程的学习,大家的C++ Qt开发技术能有质的飞越,能找到自己心仪的工作。课程中如果讲的不对的地方,请大家指出,我及时修正,我也只是一个普通开发者,也不是所有的技术都会,尽我所能,把我所会的教给大家,让我们一起为Qt的发展,尽一份绵薄之力。 下面是本课程一些项目的截图: 1 可以滑动的设置界面         2 所有图形的绘制       3 视频播放器          4 高仿youku界面         5 视频会议         相信通过本课程的学习,大家有能力实现绝大部分客户端项目,从此用C++ Qt再也不会有难写的界面。
### 回答1: 你好,以下是 Qt 官方提供的多线程示例代码: #include <QCoreApplication> #include <QThread> #include <QDebug> class Worker : public QObject { Q_OBJECT public slots: void doWork() { for (int i = ; i < 100; i++) { qDebug() << "Working on" << QThread::currentThread(); } } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QThread thread; Worker worker; worker.moveToThread(&thread); QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork); QObject::connect(&worker, &Worker::finished, &thread, &QThread::quit); QObject::connect(&worker, &Worker::finished, &worker, &QObject::deleteLater); QObject::connect(&thread, &QThread::finished, &thread, &QObject::deleteLater); thread.start(); return a.exec(); } 希望能对你有所帮助。 ### 回答2: Qt 是一个跨平台的 C++ 开发框架,其中包含了多线程支持。官方提供了许多示例代码来帮助开发者理解和使用多线程。 在 Qt多线程示例代码中,最常见的是使用 QThread 类创建新的线程。开发者可以继承 QThread 并重写 run() 方法来实现自己的线程逻辑。在 run() 方法内部,可以编写需要在新线程中执行的代码。 除了 QThread,Qt 还提供了其他更高级的类来更好地管理线程。例如,QtConcurrent 模块提供了一组高级接口,可以轻松地实现并行计算,如使用 QFuture 和 QFutureWatcher 来获得任务执行的结果或监视任务的进度。 此外,Qt 还提供了一些线程间通信的机制,例如信号与槽机制。开发者可以在不同线程中发送信号和槽,从而实现线程之间的通信。这种机制可以确保线程间的数据安全,避免了并发访问的问题。 总而言之,Qt 提供了很多丰富的多线程功能和示例代码,可以帮助开发者轻松地实现并发程序,并提高程序的性能和响应能力。开发者可以根据自己的需求和情况,选择合适的多线程类和机制来实现自己的多线程应用程序。 ### 回答3: Qt是一个跨平台的C++应用程序框架,它提供了丰富的图形界面和操作系统功能的库。Qt多线程官方示例代码主要是通过使用QtQThread类来实现多线程编程。 QThread是一个线程类,它封装了底层操作系统线程的创建和管理。它提供了一种方便的方式来创建和管理线程,并通过信号和槽机制来实现线程间的通信。在多线程编程中,通常我们会将耗时的操作放在一个独立的线程中执行,以避免阻塞主线程。 官方示例代码中,通常会创建一个新的类继承自QThread类,并重写其run()方法。在run()方法中,我们可以编写需要在新线程中执行的代码。示例代码通常会包含一些具体的功能,如下载文件、计算任务等。我们可以在这些功能中实现具体的逻辑,使用Qt提供的信号和槽机制来与其他线程进行通信。 在使用多线程时,需要注意线程的同步和互斥问题。Qt提供了一些线程安全的数据结构和类,如QMutex、QWaitCondition等,来保证多个线程之间的数据访问安全。示例代码中通常会使用这些类来确保多线程间数据的正确性和一致性。 总的来说,Qt多线程官方示例代码提供了一个通过QThread类来实现多线程编程的示例,通过这些示例我们可以学习和理解如何使用Qt框架进行多线程编程,并了解线程同步和互斥的相关知识。这对于开发需要并发处理的应用程序非常有用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王哥编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值