如何将 QProcess 的执行与 QProgressBar 的推进联系起来以实现非常繁重的计算循环

Posted

技术标签:

【中文标题】如何将 QProcess 的执行与 QProgressBar 的推进联系起来以实现非常繁重的计算循环【英文标题】:How to link the execution of QProcess and the advancement of the QProgressBar for a very heavy computation loop 【发布时间】:2020-05-13 21:32:18 【问题描述】:

我有以下 bash 脚本要通过 QPushButton 在 GUI 上执行:

 #!/bin/bash

rostopic echo -b test_LaserScan_PointCloud2_test2.bag -p /scan > test_landing_test_2.csv
rostopic echo -b test_LaserScan_PointCloud2_test2.bag -p /velodyne_points > vel_test_2.csv

脚本将遍历每个文件并提取相关的.csv。这个过程很繁重,需要一点时间。 问题是没有办法知道需要多长时间,除非我放一个QProgressBar,但我不知道如何正确链接QProcess的执行和推进QProgressBar 正确。

到目前为止,提取已成功进行,但 QProgressBar 并未从 0 开始移动。

下面我创建了一个可验证的最小示例,为了完整起见,可以在此处找到源代码:

ma​​inwindow.h

#include <QMainWindow>
#include <QProcess>

QT_BEGIN_NAMESPACE
namespace Ui  class MainWindow; 
QT_END_NAMESPACE

class MainWindow : public QMainWindow

    Q_OBJECT
    Q_PROPERTY(float progress READ progress NOTIFY progressChanged)
    Q_PROPERTY(bool running READ running NOTIFY runningChanged)
    Q_PROPERTY(bool finished READ finished NOTIFY finishedChanged)

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    float progress();
    bool running();
    bool finished();

public Q_SLOTS:
    void startComputation();
    void finishComputation();
    void updateProgress(int value);

signals:
    void progressChanged();
    void runningChanged();
    void finishedChanged();

private slots:
    void on_executeBtn_clicked();

private:
    Ui::MainWindow *ui;
    QProcess *executeBash;

    bool m_running = false;
    int m_progressValue = 0;
    bool m_finished = false;
;

ma​​inwindow.cpp

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)

    ui->setupUi(this);

this->executeBash = new QProcess(this);
    this->executeBash->setProcessChannelMode(QProcess::MergedChannels);
    /*connect(this->executeBash, &QProcess::readyReadStandardOutput, [script = this->executeBash]()
        qDebug() << "[EXEC] DATA: " << script->readAll();
    );*/
    connect(this->executeBash, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
          [script = this->executeBash](int exitCode, QProcess::ExitStatus exitStatus)
        qDebug() << "[EXEC] FINISHED: " << exitCode << exitStatus;
        if(script->bytesAvailable() > 0)qDebug() << "[EXEC] buffered DATA:" << script->readAll();
    );
    connect(this->executeBash, &QProcess::errorOccurred, [script = this->executeBash](QProcess::ProcessError error)
        qDebug() << "[EXEC] error on execution: " << error << script->errorString();
    );

    connect(this->executeBash, &QProcess::readyReadStandardOutput, [this, script = this->executeBash]()

         QString s = QString::fromUtf8(script->readAll());
         qDebug() << "[EXEC] DATA: " << s;
         auto match = QRegularExpression("Stage (\\d+)/(\\d+): (.*)").match(s);
         if (match.hasMatch()) 
             int x = match.captured(1).toInt();
             int y = match.captured(2).toInt();
             QString stage_info = match.captured(3);
             qDebug() << "x = " << x;
             qDebug() << "y = " << y;
             qDebug() << "info = " << stage_info;
             this->updateProgress(x * 100 / y);
         
    );

    // Initialization of the progressbar
    m_running = false;
    emit runningChanged();
    m_finished = false;
    emit finishedChanged();
    updateProgress(0);



MainWindow::~MainWindow()

    delete ui;



// ProgressBag loading depending on the workload of the .sh file
float MainWindow::progress()

  return m_progressValue;


bool MainWindow::running()

  return m_running;


bool MainWindow::finished()

  return m_finished;


void MainWindow::startComputation()

  m_running = true;
  emit runningChanged();
  updateProgress(100);


void MainWindow::finishComputation()

  m_finished = true;
  emit finishedChanged();

  m_running = false;
  emit runningChanged();


void MainWindow::updateProgress(int value)

  m_progressValue = value;
  emit progressChanged();

  if (m_progressValue == 100)
      finishComputation();

  ui->progressBarExecuteScript->setValue(value);




void MainWindow::on_executeBtn_clicked()

    qDebug() << "Button clicked!";
    this->executeBash->start(QStringLiteral("/bin/sh"), QStringList() << QStringLiteral("/home/emanuele/catkin_docking_ws/devel/lib/test.sh")); //will start new process without blocking
    // right after the execution of the script the QProgressBar will start computing
    //ui->progressBarExecuteScript->setValue(executeBash->readAll().toInt());

到目前为止我做了什么

1) 设置完所有必要的点后,我遇到了this source,用户遇到了与我现在相同的问题,但通过QDir 解决了,不过我不确定这个解决方案。

2) 在查看了官方文档后,我发现了future watcher。我是这个工具的新手,很难理解如何应用它,这也是我创建最小可验证示例的原因。

3)我尝试setValue的进度条基于bash文件的执行。或者更好的推进如下图:

ui->progressBarExecuteScript->setValue(executeBash->readAll().toInt());

我认为这可以解决问题,但 QProgressBar 保持在 0 值,我不知道为什么。

4)我查阅了QProgressBar官方文档,但没有找到任何可以帮助我解决问题的东西。

请指出正确的方向来解决这个问题。

【问题讨论】:

我认为不可能获得QProcess 的推进,因为您不知道该过程可以存在多少时间。所以一个可能的解决方案是将进度条的最小值、最大值和当前值设置为 0,以获得无限的进度 感谢您阅读问题。您的意思是在此期间无限进展正在工作,何时完成将值设置为 100? @Emanuele QProgressBar 只显示进度(它是一个小部件),它不计算它。计算的任务是你的工作,例如,如果你下载 100 个文件,那么你可以指出每个文件是 1%,但另一个开发人员可以计算所有文件的大小(以字节为单位),然后将进度计算为下载百分比bytes 显然这两个数字是不同的,所以如果你不知道如何计算它,那么其他人是不可能帮助你的。在这些中最好设置progressbar-&gt;setRange(0, 0),这将成为一个忙碌的酒吧 @eyllanesc,感谢您阅读问题。问题是我不知道第一个命令何时结束(即我不知道提取.csv 的物理时间)并且我不知道第二个命令何时结束(即开始第二次提取及其持续时间)将发生。 看来QProcess不是一个好选择? 【参考方案1】:

首先,修改脚本以输出当前正在执行的阶段:

#!/bin/bash
set -e
echo "Stage 1/2: Scan"
rostopic echo -b test_LaserScan_PointCloud2_test2.bag -p /scan > test_landing_test_2.csv
echo "Stage 2/2: Velodyne"
rostopic echo -b test_LaserScan_PointCloud2_test2.bag -p /velodyne_points > vel_test_2.csv

我还添加了set -e 以使其在第一个失败的命令时停止。

现在,您可以在输出流中查找这些“Stage x/y”标记,如果是,则调用updateProgress

connect(this->executeBash, &QProcess::readyReadStandardOutput, [this, script = this->executeBash]()
     QString s = QString::fromUtf8(script->readAll());
     auto match = QRegularExpression("Stage (\\d+)/(\\d+): (.*)").match(s);
     if (match.hasMatch()) 
         int x = match.captured(1).toInt();
         int y = match.captured(2).toInt();
         QString stage_info = match.captured(3);
         this->updateProgress(x * 100 / y);
     
);

【讨论】:

感谢您访问并阅读问题。您的解决方案部分有效,实际上在终端上我可以看到两个标志正在正确处理:)。但是 QProgressBar 不会从 0 开始移动。我错过了什么吗?我更新了问题,添加了正确触发 QProcess 的答案 我认为你的第一个回调(使用[EXEC] DATA)在第二个回调之前吃掉了输出。 我找到了丢失的部分!基本上我没有正确更新进度条并在函数updateProgress 中添加了ui-&gt;progressBarExecuteScript-&gt;setValue(value);。也是的,你是对的,第一个回调(带有[EXEC] DATA)正在吃掉输出。 我在问题中发布了正确的工作代码,添加了您的建议并添加了进度条的缺失部分。感谢您的帮助和时间,非常感谢!另外,如果您认为我的问题很清楚,请考虑给我一个大拇指:)。

以上是关于如何将 QProcess 的执行与 QProgressBar 的推进联系起来以实现非常繁重的计算循环的主要内容,如果未能解决你的问题,请参考以下文章

如何在 QProcess 执行它们时打印真正的 QProcess 参数列表

和号(&)(非常命令执行)与 QProcess

进程结束时如何保持 QProcess 执行的命令?

如何检查 QProcess 是不是正确执行?

当进程结束时,如何保持QProcess执行的命令?

如何使用 QProcess 同时运行多个 python 脚本