Qt - Как объединить QtConcurrent и QThreadPool для QProgressBar?

В mainwindow.ui я создал QProgressBar с именем progressBar и QPushButton с именем speckle, который запускает тяжелые вычисления.

Внутри mainwindow.h у меня есть соответствующий private slot для кнопки и частная функция, которая представляет тяжелые вычисления. 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

};

Предполагается, что futureWatcher отслеживает объект QFuture, возвращаемый 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();
}

Но приложение так не работает. После компиляции и нажатия пятна mainwindow не отвечает. Вот функция-член progressSpecle, в которой создается x потоков:

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

}

Приложение работает без ошибок, но mainWindow не виноват, потому что (я думаю) while(true). Но я не понимаю, почему это должно блокировать mainWindow, потому что вся функция progressSpecle работает в отдельном потоке, созданном и начатом с QtConcurrent.

Почему функция progressSpecle блокирует mainWindow? Итак, как я могу заставить progressBar работать?


person goulashsoup    schedule 20.02.2017    source источник
comment
Немного не по теме, но... если specklevisualization каким-либо образом наследуется от QWidget, вы не можете создать его экземпляр в потоке без графического интерфейса.   -  person G.M.    schedule 20.02.2017
comment
@G.M. Я уже изменил функцию progressSpecle, чтобы она возвращала вектор result.   -  person goulashsoup    schedule 20.02.2017
comment
Вы создали объект приложения перед конструктором MainWindow? Выводит ли QObject::connect() ошибку в окно консоли?   -  person falkb    schedule 20.02.2017
comment
@falk Вы имеете в виду, что я позвонил QApplication a(argc, argv); до MainWindow m; m.show();? Тогда да. Нет QObject::connect() не печатает ошибки. Почему? Ошибки нет, но mainWindow не отвечает... Я обновил вопрос, чтобы стало ясно, какая у меня проблема....   -  person goulashsoup    schedule 20.02.2017
comment
Код (сильно отредактированный) имеет несколько очевидных проблем. on_speckle_clicked вызывается в потоке GUI, но блокируется при вызове QThreadPool::waitForDone — это не сработает. Также каждому вновь созданному cv_speckle_algorithm передается ссылка на results, намекая на то, что несколько потоков будут одновременно писать в results. Это, безусловно, проблема. Могу ли я предложить вам сначала забыть о QThreadPool и заставить базовые алгоритмы работать в однопоточном режиме, а затем посмотреть на использование `QThreadPool.   -  person G.M.    schedule 20.02.2017
comment
@G.M. Спасибо за совет. Решение Oktalist работало с небольшими изменениями. Я не реализовал cv_speckle_algorithm, но ваше опасение, что несколько потоков будут одновременно записывать результаты, верно, и я думаю, что иногда это приводит к ошибке, поэтому я обязательно вернусь к этой проблеме... Спасибо   -  person goulashsoup    schedule 20.02.2017


Ответы (1)


Сигнал QFutureWatcher испускается внутри объединенного потока. Это означает, что слот QProgressBar будет вызываться через «соединение в очереди»: событие будет помещено в очередь цикла событий основного потока, и слот будет вызываться при обработке этого события.

Вызов QThreadPool::waitForDone блокирует основной поток, поэтому цикл событий не выполняется, и слот в очереди не будет вызываться. Вам нужно поддерживать цикл обработки событий основного потока, пока он ожидает завершения параллельной задачи.

Есть два способа, как я могу это сделать. Первый — подключить обратный вызов к сигналу QFutureWatcher::finished и вернуть управление в основной цикл обработки событий:

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
}

Вы можете использовать именованный метод вместо лямбда, если хотите.

Второй способ — запустить вложенный цикл событий внутри вашей функции и соединить его слот quit с сигналом QFutureWatcher::finished:

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();
}
person Oktalist    schedule 20.02.2017
comment
В функции connect необходимо использовать шаблон: &QFutureWatcher‹std::vector‹cv::Mat››::finished. Шаблон ‹std::vector‹cv::Mat›› необходим, поскольку вы не можете использовать шаблон класса или универсальный класс в качестве идентификатора без шаблона или общего списка аргументов. -> приводит к ошибке компилятора C2955 - person goulashsoup; 20.02.2017
comment
Рад, что это сработало. Спасибо за ответ. Я исправил ошибку немного по-другому, как видите, мне кажется, так красивее. - person Oktalist; 20.02.2017