如何link QProcess 的执行和 QProgressBar 的推进用于非常繁重的计算循环

How to link the execution of QProcess and the advancement of the QProgressBar for a very heavy computation loop

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

 #!/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,但我不知道link的执行有多正确QProcessQProgressBar 的进步正确。

截至目前提取成功,但 QProgressBar 并未从 0 移动。

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

mainwindow.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;
};

mainwindow.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官方文档,但没有找到任何可以帮助我解决问题的东西。

请指出解决此问题的正确方向。

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

#!/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);
     }
});