前言
在软件开发中,经常会遇到需要异步执行一系列耗时步骤的场景。当前一个操作失败时,我们希望停止后续操作;而如果成功,我们则继续执行下一步。这种情况常见于编译、构建等操作。比如编译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