0

長い計算中に QProgressBar を更新する方法を説明するチュートリアルをオンラインでいくつか見つけました。そのうちの 1 つは、QThread を使用して計算を行い、次に に接続されているシグナルを発行することですprogressBar.setValue(int)

これは、同時に実行される複数の QThread でも機能するはずだと思いましたが、何かが正しく機能していません。

だから、これが私がすることです:私はいくつかの粒子の軌道を計算したいです(それぞれが長いループを持っています)。マルチコア処理を使用するには、これらのパーティクルごとに QThread を作成し、それぞれの計算メソッドを呼び出します。これは問題なく動作し、すべてのコアが使用され、計算は以前よりも約 4 分の 1 の時間で終了します。

このチュートリアル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すべてのスレッドを開始する関数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
    }
}

パブリック スロットupdateProgress(int)は、1000 回目の繰り返しステップごとにここで呼び出されます。次のように、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 <-> Worker 接続にまだ何か問題がありますか?

4

2 に答える 2

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 オブジェクトに移動する必要があります。

于 2014-06-11T15:56:03.520 に答える
1

IMOこれは間違っています。スレッドの作成にはコストがかかり、多くのスレッドを作成する必要があります。まず、QThreadPoolを使用する必要があります。ケースは、このクラスの機能と正確に一致します。

ボイラー プレート コードを大幅に削減するQtConcurrentメソッドもあります(これは Qt の放棄された機能であるため、QThreadPool を使用することをお勧めしますが、非常にうまく機能することを試すことができます)。

于 2014-06-12T15:59:20.893 に答える