【问题标题】:Qt - How to combine QtConcurrent and QThreadPool for a QProgressBar?Qt - 如何为 QProgressBar 结合 QtConcurrent 和 QThreadPool?
【发布时间】:2017-07-09 20:49:28
【问题描述】:

mainwindow.ui 中,我创建了一个名为progressBarQProgressBar 和一个名为speckleQPushButton,这将启动繁重的计算。

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 应该监视QtConcurrent 返回的QFuture 对象:

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

但应用程序不能那样工作。编译并单击 on speckle 后,mainwindow 没有响应。这是创建 x Threads 的 progressSpecle 成员函数:

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)。但我不明白为什么这会阻塞主窗口,因为整个progressSpecle 函数在一个单独的线程中工作,该线程以QtConcurrent 创建并开始。

为什么progressSpecle 函数会阻塞主窗口? 那么我怎样才能让progressBar 工作呢?

【问题讨论】:

  • 有点跑题了,但是...如果specklevisualization 以任何方式从QWidget 继承,那么您不能在非GUI 线程上创建它的实例。
  • @G.M.我已经更改了 progressSpecle 函数,以便它返回 result 向量。
  • 您是否在 MainWindow 构造函数之前创建了应用程序对象? QObject::connect() 是否将错误打印到控制台窗口?
  • @falk 你的意思是我在MainWindow m; m.show(); 之前打电话给QApplication a(argc, argv); 吗?好的。否 QObject::connect() 不会打印错误。为什么要呢?没有错误,但 mainWindow 没有响应......我更新了问题,以便清楚我遇到什么样的问题......
  • (大量编辑的)代码有几个明显的问题。 on_speckle_clicked 在 GUI 线程上被调用,但在调用 QThreadPool::waitForDone 时被阻塞——这不起作用。此外,每个新创建的cv_speckle_algorithm 都会传递一个对results 的引用,暗示多个线程将同时写入results。这当然是个问题。我可以建议您一开始忘记QThreadPool,让基本算法以单线程方式工作,然后然后考虑使用`QThreadPool。

标签: c++ qt qthread qtconcurrent qprogressbar


【解决方案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
}

如果您愿意,可以使用命名方法而不是 lambda。

第二种方法是在你的函数中运行一个嵌套的事件循环,并将它的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();
}

【讨论】:

  • connect 函数中需要使用模板:&QFutureWatcher<:vector>>::finished。模板 <:vector>> 是必需的,因为“如果没有模板或泛型参数列表,就不能使用类模板或类泛型作为标识符。” -> 导致编译器错误 C2955
  • 很高兴它成功了。感谢您的反馈。如您所见,我以稍微不同的方式修复了错误,我认为它看起来更漂亮。
猜你喜欢
  • 1970-01-01
  • 2012-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-09
相关资源
最近更新 更多