Я нашел в Интернете несколько руководств, в которых объясняется, как обновить QProgressBar во время долгих вычислений. Один из них: использовать QThread для вычисления, а затем испустить сигнал, который подключен к progressBar.setValue(int)
.
Я думал, что это также должно работать для нескольких QThread, которые работают одновременно, но что-то работает неправильно.
Итак, вот что я делаю: я хочу вычислить траектории нескольких частиц (каждая с длинным циклом). Чтобы использовать многоядерную обработку, я создаю QThread для каждой из этих частиц и позволяю ей вызывать соответствующий метод расчета. Это работает нормально, используются все ядра, и расчет завершается примерно за четверть времени, чем раньше.
Я написал класс Worker на основе этого руководства http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/. Заголовок выглядит так: (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
, которая вызывается Worker:
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-ю итерацию. Он подключен к QProgressBar в моем MainWindow следующим образом:
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?
Worker
, который живет в отдельном потоке, вызывает методWorld
, который живет в основном потоке и пытается послать сигнал, заставляет меня нервничать. СигналupdateProgress
действительно должен быть сигналом для рабочего, чтобы вызовconnect
распознал его как межпоточный сигнал. Не уверен, что это проблема, но из того, что я знаю о потоковой модели Qt, ваш код не кошерный. - person Sebastian Redl   schedule 11.06.2014World
иWorker
кажутся очень тесно связанными. Подумайте о перепроектировании вашегоWorld
класса, чтобы он ничего не знал о вашемWorker
классе и ничего не об итерациях. Переместите итерации и излучение сигнала в классWorker
, а затем подключите его к индикатору выполнения. - person RobbieE   schedule 11.06.2014