Search code examples
c++qtqthreadqtconcurrentqprogressbar

Qt - How to combine QtConcurrent and QThreadPool for a QProgressBar?


In the mainwindow.ui I created a QProgressBar named progressBar and a QPushButton named speckle which starts the heavy computation.

Inside the mainwindow.h I have an appropriate private slot for the button and a private function which represents the heavy computation. mainwindow.h:

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_speckle_clicked();
    ...

private:
    Ui::MainWindow *ui;
    QFutureWatcher<std::vector<cv::Mat>> futureWatcher;
    std::vector<cv::Mat> progressSpecle();//heavy computation

};

The futureWatcher is supposed to watch the the QFuture object that gets returned from QtConcurrent:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ...
    connect(&this->futureWatcher, SIGNAL(progressValueChanged(int)), ui->progressBar, SLOT(setValue(int)));
    ...
}

...

void MainWindow::on_speckle_clicked()
{    
    //Start the computation.
    QFuture<std::vector<cv::Mat>> future;
    future = QtConcurrent::run(this, &MainWindow::progressSpecle);
    this->futureWatcher.setFuture(future);

    QThreadPool::globalInstance()->waitForDone();

    vector<cv::Mat> result = future.result();

    specklevisualization *s = new specklevisualization;
    s-> setAttribute(Qt::WA_DeleteOnClose);
    s-> start(result);
    s-> show();
}

But the the application does not work like that. After compiling and clicking on speckle the mainwindow is not responsive. Here is the progressSpecle member function in which x Threads gets created:

void MainWindow::progressSpecle(){
    vector<cv::Mat> input;
    ...//do something with input

    vector<cv::Mat> result;
    vector<cv::Mat> *all;
    all = &result;

   QThreadPool *threadPool = QThreadPool::globalInstance();

    for(unsigned int i = 1; i<input.size(); i++) {
        cv_speckle_algorithm *work = new cv_speckle_algorithm(input.at(i-1), input.at(i), all, i-1);
        work->setAutoDelete(false);
        threadPool->start(work);
    }

    while(true){
        if(threadPool->activeThreadCount() == 1) return result;
    }

}

The application works without errors but the mainWindow is not responsible because (I think) of the while(true). But I do not understand why this should block the mainWindow, because the whole progressSpecle function works in a seperate thread created and started with QtConcurrent.

Why does the progressSpecle function Blocks the mainWindow? So how can I get the progressBar to work?


Solution

  • The QFutureWatcher signal is emitted from within the pooled thread. This means that the QProgressBar slot will be called via a "queued connection": an event will be queued to the main thread's event loop, and the slot will be called when this event is processed.

    The call to QThreadPool::waitForDone is blocking the main thread, so the event loop is not running and the queued slot will not be called. You need to keep the main thread's event loop running while it waits for the concurrent task to finish.

    There are two ways I can think to accomplish this. The first is to connect a callback to the QFutureWatcher::finished signal and return control to the main event loop:

    void MainWindow::on_speckle_clicked()
    {
        //Start the computation.
        QFuture<std::vector<cv::Mat>> future;
        future = QtConcurrent::run(this, &MainWindow::progressSpecle);
    
        connect(&futureWatcher, &QFutureWatcherBase::finished, this, [result] {
            vector<cv::Mat> result = future.result();
            specklevisualization *s = new specklevisualization;
            s->setAttribute(Qt::WA_DeleteOnClose);
            s->start(result);
            s->show();
        });
        this->futureWatcher.setFuture(future);
    
        // return control to event loop
    }
    

    You can use a named method instead of a lambda if you prefer.

    The second way is to run a nested event loop inside your function, and connect its quit slot to the QFutureWatcher::finished signal:

    void MainWindow::on_speckle_clicked()
    {
        QEventLoop localLoop;
    
        //Start the computation.
        QFuture<std::vector<cv::Mat>> future;
        future = QtConcurrent::run(this, &MainWindow::progressSpecle);
    
        connect(futureWatcher, &QFutureWatcherBase::finished, &localLoop, &QEventLoop::quit);
        this->futureWatcher.setFuture(future);
    
        localLoop.exec(); // wait for done
    
        vector<cv::Mat> result = future.result();
        specklevisualization *s = new specklevisualization;
        s->setAttribute(Qt::WA_DeleteOnClose);
        s->start(result);
        s->show();
    }