В интернете я нашел несколько учебных пособий, объясняющих, как обновлять 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
это называется Работником:
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. Что-то не так с миром? <-> Рабочие связи?
ИМО это неправильно. Создание темы стоит дорого, и вы хотите создать много из них.
Прежде всего вы должны использовать QThreadPool, ваш случай точно соответствует функциональности этого класса.
Это также QtConcurrent методы, которые значительно уменьшат код котельной пластины (это заброшенная особенность Qt, поэтому рекомендуется использовать QThreadPool, но вы можете попробовать, что он работает довольно хорошо).
Проблема заключается в том, что для обработки излучаемого сигнала необходимо разрешить запуск цикла обработки событий в новом потоке. Оставаясь в цикле for в runParticle, этого не произойдет, пока функция не завершится.
Есть грубый метод, чтобы исправить это, который заключается в вызове 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 processingemit 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, содержащий указатель на мир, он напрямую вызывает функцию runParticle, которая все еще находится в главном потоке. Хотя это небезопасно, это также означает, что функция runParticle обрабатывается в основном потоке.
Вам нужно переместить функцию runParticle в объект Worker, который находится в новом потоке.