1

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?

goulashsoup
  • 2,639
  • 2
  • 34
  • 60
  • Slightly off topic but... if `specklevisualization` inherits in any way from `QWidget` then you can't create an instance of it on a non-GUI thread. – G.M. Feb 20 '17 at 14:56
  • @G.M. I allready changed the `progressSpecle` function so that it returns the `result` vector. – goulashsoup Feb 20 '17 at 15:14
  • Did you create the application object before your MainWindow constructor? Does QObject::connect() prints an error to the console window? – falkb Feb 20 '17 at 15:58
  • @falk Do you mean that I called `QApplication a(argc, argv);` before `MainWindow m; m.show();` ? Then yes. No `QObject::connect()` prints no error. Why should it? There is no error but the mainWindow is not responsive... I updated the question so that its clear what kind of problem I have.... – goulashsoup Feb 20 '17 at 16:03
  • 2
    The (heavily edited) code has several obvious problems. `on_speckle_clicked` is invoked on the GUI thread but blocks on a call to `QThreadPool::waitForDone` -- that won't work. Also each newly created `cv_speckle_algorithm` is passed a reference to `results` hinting that multiple threads will be writing to `results` concurrently. That's certainly an issue. Can I suggest that, initially, you forget about `QThreadPool` and get the basic algorithms working in a single threaded fashion and *then* look at using `QThreadPool. – G.M. Feb 20 '17 at 16:13
  • @G.M. Thanks for advise. The solution of [Oktalist](http://stackoverflow.com/users/1639256/oktalist) worked with a little change. I did not implement `cv_speckle_algorithm` but your concern that _multiple threads will be writing to results concurrently_ is right and I think it results in an error sometimes so I definitively will come back to that problem... Thx – goulashsoup Feb 20 '17 at 17:01

1 Answers1

2

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();
}
Oktalist
  • 14,336
  • 3
  • 43
  • 63
  • In the `connect` function it is necessary to use a template: &QFutureWatcher>::finished. The template > is necessary because "You cannot use a class template or class generic as an identifier without a template or generic argument list." -> Results in an Compiler Error C2955 – goulashsoup Feb 20 '17 at 17:31
  • 1
    Glad it worked. Thanks for the feedback. I fixed the error in a slightly different way as you can see, I think it looks prettier. – Oktalist Feb 20 '17 at 18:04