从多个QThreads更新QProgressBar

时间:2014-06-11 15:04:31

标签: c++ qt qthread qprogressbar

我在网上找到了几个教程,解释了在一些长时间计算过程中如何更新QProgressBar。其中之一是:使用QThread进行计算,然后发出连接到progressBar.setValue(int)的信号。

我认为这也必须适用于同时运行的多个QThread,但有些东西无法正常工作。

所以,这就是我所做的:我想计算几个粒子的轨迹(每个粒子都有一个长循环)。为了使用多核处理,我为每个粒子创建一个QThread并让它调用相应的计算方法。这样可以正常工作,使用所有核心,计算结束的时间大约是以前的四分之一。

我根据本教程http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/编写了一个Worker类。标题看起来像这样: (worker.h)

#include <world.h>

class Worker: public QObject
{
    Q_OBJECT

public:
    explicit Worker(World *world = 0, double deltaTau = 0., double maxDist = 0., double iterations = 0., double index = 0);

public slots:
    void process();

signals:
    void finished();
    void eror(QString err);

private:
    World *w;
    double my_deltaTau;
    double my_maxDist;
    int my_iterations;
    int my_index;
};

源代码如下: (worker.cpp)

#include "worker.h"

Worker::Worker(World *world, double deltaTau, double maxDist, double iterations, double index)
{
    w = world;
    my_deltaTau = deltaTau;
    my_maxDist = maxDist;
    my_iterations = iterations;
    my_index = index;
}

void Worker::process()
{
    w->runParticle(my_deltaTau, my_maxDist, my_iterations, my_index);
    emit finished();
}

在world.cpp中,我有一个函数run,它启动所有线程和由Worker调用的函数runParticle

void World::run(double deltaTau, double maxDist, int iterations)
{
    globalProgress = 0;
    for (int j = 0; j < particles->size(); j++) { //loop over all particles
        QThread *thread = new QThread;
        Worker *worker = new Worker(this, deltaTau, maxDist, iterations, j);
        worker->moveToThread(thread);
        connect(thread, SIGNAL(started()), worker, SLOT(process()));
        connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
        connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()));
        connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()));
        thread->start();
    }
}

void World::runParticle(double deltaTau, double maxDist, int iterations, int index)
{
    for (int i = 0; i < iterations; i++) { //loop over iteration steps
        if (i % 1000 == 0) { //only update the progress bar every 1000th iteration
            emit updateProgress(++globalProgress);
            qApp->processEvents(); // <--- I added this line, no effect!
        }
        [...] // <--- do my calculations for the particle's trajectories
    }
}

每1000次迭代步骤调用公共插槽updateProgress(int)。它连接到我的MainWindow中的QProgressBar,如下所示:

progressBar->setValue(0);
progressBar->setMaximum(nrPart * iter / 1000); //number of particles * number of iteration steps / 1000
connect(world, SIGNAL(updateProgress(int)), progressBar, SLOT(setValue(int)));
world->run(timeStep, dist, iter);

我的问题是进度条在所有计算完成之前都没有移动,然后我看到它很快就会移动到100%。

有人看到我的错误或知道如何正确地做到这一点吗?

修改

我做了以下更改:

(worker.h)

#include "world.h"

class Worker: public QObject
{
    Q_OBJECT

public:
    explicit Worker(World *world = 0, Particle *particle = 0, QList<MagneticField> *bfields = 0, double deltaTau = 0., double maxDist = 0., int iterations = 0);

public slots:
    void process();

signals:
    void finished();
    void updateProgress(int value);
    void ProcessParticle();
    void eror(QString err);

private:
    int i;
    Particle *p;
    QList<MagneticField> *magneticFields;
    double my_deltaTau;
    double my_maxDist;
    int my_iterations;
};

(worker.cpp)

#include "worker.h"

Worker::Worker(World *world, Particle *particle, QList<MagneticField> *bfields, double deltaTau, double maxDist, int iterations)
{
    i = 0;
    const World *w = world;
    p = particle;
    magneticFields = bfields;
    my_deltaTau = deltaTau;
    my_maxDist = maxDist;
    my_iterations = iterations;
    connect(this, SIGNAL(updateProgress(int)), w, SLOT(updateTheProgress(int)));
    connect(this, SIGNAL(ProcessParticle()), this, SLOT(process()), Qt::QueuedConnection);
}

void Worker::process()
{
    const int modNr = my_iterations / 1000;
    QDateTime start = QDateTime::currentDateTime();
    while (i < my_iterations) { //loop over iteration steps
        [...] // <--- do my calculations
        //handle progress
        emit updateProgress(1);
        if (QDateTime::currentDateTime() > start.addMSecs(300)) {
            emit ProcessParticle();
            ++i; //ensure we return to the next iteration
            return;
        }
        i++;
    }
    qDebug() << "FINISHED"; // <--- I can see this, so finished() should be emitted now...
    emit finished();
}

(world.h的一部分)

public slots:
    void threadFinished();
    void updateTheProgress(int value);

signals:
    void updateProgress(int value);

(world.cpp的一部分)

void World::threadFinished()
{
    particleCounter++;
    qDebug() << "particles finished: " << particleCounter; // <--- this is NEVER called !?!?
    if (particleCounter == particles->size()) {
        hasRun = true;
    }
}

void World::updateTheProgress(int value)
{
    globalProgress += value;
    emit updateProgress(globalProgress);
}

void World::run(double deltaTau, double maxDist, int iterations)
{
    globalProgress = 0;
    particleCounter = 0;
    hasRun = false;
    for (int i = 0; i < particles->size(); i++) { //loop over all particles
        QThread *thread = new QThread;
        Worker *worker = new Worker(this, &(*particles)[i], bfields, deltaTau, maxDist, iterations);
        worker->moveToThread(thread);
        connect(thread, SIGNAL(started()), worker, SLOT(process()));
        connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
        connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()));
        connect(worker, SIGNAL(finished()), this, SLOT(threadFinished())); // <--- this connection SHOULD make sure, I count the finished threads
        connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()));
        thread->start();
    }
}

(在MainWindow.cpp的某处)

progressBar->setValue(0);
progressBar->setMaximum(nrPart * iter);
connect(world, SIGNAL(updateProgress(int)), progressBar, SLOT(setValue(int)));
world->run(timeStep, dist, iter);
while (!world->hasBeenRunning()) {} //wait for all threads to finish

正如我在上面的代码中标记的那样,当线程完成并且我最终在MainWindow中的无限循环中时,我从未收到通知。 World&lt; - &gt;还有问题吗?工人联系?

2 个答案:

答案 0 :(得分:1)

问题是,为了处理发出的信号,您需要允许事件循环在新线程上运行。通过保留在runParticle中的for循环中,该函数没有发生,直到函数完成。

有一种粗略的方法可以解决这个问题,即在循环过程中每隔一段时间调用一次QApplication :: processEvents。

更好的方法是重新设计对象,以便在退出并允许事件循环自然运行之前处理多个迭代。

因此,要创建一个时间片进行处理,在for循环中,您需要花费多长时间进行迭代。如果时间超过1/30秒,则调用QueuedConnection类型的信号再次调用插槽功能并退出for循环。

QueuedConnection将确保处理任何事件,然后再次调用您的函数。

假设runParticle是一个插槽: -

void Worker::runParticle(...)
{
    static int i = 0;

    QDateTime start = QDateTime::currentDateTime();

    for(i; i < iteration; ++i)
    {
        // do processing


        emit updateProgress(++globalProgress);

        // check if we've been here too long
        if(QDateTime::currentDateTime() > start.addMSecs(300))
        {
          emit ProcessParticle(); // assuming this is connected to runParticle with a Queued Connection
          ++i; // ensure we return to the next iteration
          return;
        }
    }
}

另一方面,当一个对象被移动到一个新线程时,它的所有子节点也会被移动,其中一个子节点是QObject层次结构的一部分。

通过让Worker对象保持指向World的指针,它直接调用World的runParticle函数,该函数仍在主线程上。虽然这是不安全的,但这也意味着正在主线程上处理runParticle函数。

您需要将runParticle函数移动到新对象上的Worker对象。

答案 1 :(得分:1)

IMO这是错误的。创建一个线程是昂贵的,你想创建它们很多。 首先,您应该使用QThreadPool,您的案例与此类的功能完全匹配。

还有QtConcurrent种方法可以大大减少锅炉板代码(这是Qt的废弃功能,所以建议使用QThreadPool,但你可以尝试使用它很好。)