QThread的应用——在线程里面更新QProgressBar进度条

在线程里面更新QProgressBar进度条

编写QT软件的时候,经常会遇到点击某个按钮,进行一个比较耗时的计算。为了在计算过程中,软件界面就继续响应用户的点击,不会有卡死的感觉,一般会将这个耗时的计算放在另外一个线程里面,同时在界面上布置一个进度条(QProgressBar),显示当前的计算进度,提高软件的界面以及响应性。

下面就这一看似简单实则暗藏玄机的编程过程进行抽丝剥茧的解释。

代码实现

软件的界面比较简单,如下图所示:
QThread的应用——在线程里面更新QProgressBar进度条_第1张图片

使用QT时,在线程里面执行计算的时候,修改界面元素,有多种方法,最常见的就是两种:1)继承QThread,然后重写run,把耗时的计算代码放在run函数里面,同时定义进度更新的信号;2)定义一个继承自QObject的Worker类,里面定义进度更新信号,然后使用moveToThread的方法,将该类绑定到另外一个线程,在主线程里面,将按钮的信号和Worker的槽函数连接后,按钮信号的触发,就会触发Worker的槽函数在其绑定的线程里面执行。

使用moveToThread方法

下面是使用moveToThread的方法来定义一个Worker类:

class Worker : public QObject
{
    Q_OBJECT
  public:
    ~Worker() { std::cout << "worker is destructed!\n"; }

  public slots:
    void stop() { m_stopped = true; }

    int long_time_compution()
    {
        m_stopped = false;
        std::cout << "start long time compution ...\n";
        int i = 0;
        while (!m_stopped && i++ < 100)
        {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            emit progressValueUpdated(i);
            std::cout << std::format("i: {}\n", i);
        }
        std::cout << std::format("finish long time compution, result: {}\n", i);
        emit resultReady(QString("Result: %1").arg(i));
        return i;
    }

  signals:
    void progressValueUpdated(int);
    void resultReady(QString);

  private:
    std::atomic_bool m_stopped{false};
};

我们定义了两个槽函数start和stop,以及两个信号progressValueUpdated和resultReady。

再看一下界面窗口的定义以及如何连接信号和槽函数:

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QMainWindow win;
    auto widget = new QWidget;
    auto layout = new QVBoxLayout;
    widget->setLayout(layout);
    win.setCentralWidget(widget);
    QProgressBar *bar = new QProgressBar;
    layout->addWidget(bar);
    auto btn_start = new QPushButton("Start");
    layout->addWidget(btn_start);
    auto btn_stop = new QPushButton("Stop");
    layout->addWidget(btn_stop);
    auto result_label = new QLabel("Result:");
    layout->addWidget(result_label);
    
    // 下面的worker要绑定的线程
    QThread worker_thread; 
    Worker worker;
    // 将worker绑定到单独的线程
    worker.moveToThread(&worker_thread);
    
    // 开始按钮的点击信号和worker的start槽函数连接起来
    QObject::connect(btn_start, &QPushButton::clicked, &worker, &Worker::long_time_compution);
    // 停止按钮的点击信号和worker的stop槽函数连接起来
    QObject::connect(btn_stop, &QPushButton::clicked, &worker, &Worker::stop, Qt::DirectConnection);
    // worker的进度更新信号和界面上的进度条连接起来
    QObject::connect(&worker, &Worker::progressValueUpdated, bar, &QProgressBar::setValue);
    QObject::connect(&worker, &Worker::resultReady, result_label, &QLabel::setText);
    
    // 开启工作线程的事件循环
    worker_thread.start();

    win.show();
    app.exec();
    
    // 退出工作线程的事件循环
    worker_thread.quit();
    // 等待线程当前执行的任务完成并退出
    worker_thread.wait();
}

上面的代码第一部分是界面设置,第二部分的连接界面和Worker的信号与槽,需要注意的是QObject::connect(btn_stop, &QPushButton::clicked, &worker, &Worker::stop, Qt::DirectConnection)这里最后一个参数是Qt::DirectConnection——直接连接,意思是在信号发送者的线程中调用接收者的槽函数。默认的参数是Qt::AutoConnection,如果发送者和接收者不在同一个线程中,会使用Qt::QueuedDirection,槽函数的调用会在接收者所在的线程中执行;如果两个在同一个线程中,那就是Qt::DirectConnection,槽函数是立即在发送者的线程中调用的。这里指定为Qt::DirectConnection是必须的,首先因为调用的函数是非耗时的,且线程安全(修改的变量是atomic_bool),所以不会出现多线程读写同步的问题;另外,如果不使用Qt::DirectConnect,那么默认会使用Qt::QueuedConnection,停止按钮点击时,触发一个调用Worker::stop的事件,该事件需要work_thread进行处理,可work_thread此时正在进行耗时计算,只有当计算完毕之后,才会处理事件,调用stop,显然已经没有意义了,stop的目的就是在计算过程当中来随时终止计算。

你可能感兴趣的:(c++,qt,c++,开发语言)