PDA

View Full Version : GUI freezes in loop: Thread does not help?



Matty23
25th November 2010, 17:26
Hello everybody,

I'm quite new to Qt, but I'm going to start to love it. Unfortunately I have a problem I could not solve by myself, even not after some days of thinking and experiments. So it would be very nice if someone could help me with expert knowledge, please.

My scenario:
I have a loop in which I am calculating some stuff. When the user presses a button to start it the GUI freezes until the loop is done. In many forums they say "please use qApp->processEvents()" and it will be fine.

But:
When I do not use "qApp->processEvents()" the loop takes about 2 or 3 secs. With "qApp->processEvents()" it takes about 1 minute!! (if I call "qApp->processEvents()" in every loop step). When I use "qApp->processEvents()" only every 100.000 step the GUI starts to "stutter" again.

My dream:
The user presses a button. The loop begins to solve the problem, ends after the 2 or 3 seconds. During the loop calculation the user can continue to use the GUI without any "stuttering".

My example code:
The user can press button A to increment an int-value, lets say "a". Two other buttons start a loop in which another int-value - say b - is incremented. When the user starts the "b-loop" it is not possible to press Button A until the "b-loop" is done. This is not what I want.
A solution without a thread and with a thread is included (the "two buttons"). Both do not behave as I would like it.

I am very thankful for any help! :)

Regards
Matty23




#include <QThread>

#include "qt_thread.h"

int a = 0;
int b = 0;

Qt_Thread *q;

class MyThread : public QThread
{
public:
void run();
};

void MyThread::run()
{
b = 0;

while (b < 1000000){
b++;
q->setb(b);

//qApp->processEvents();
}
exec();
}



Qt_Thread::Qt_Thread(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);

q = this;

connect(ui.pbIncA, SIGNAL(clicked()), this, SLOT(pbIncAClicked()));
connect(ui.pbWithoutThread, SIGNAL(clicked()), this, SLOT(pbWithoutThreadClicked()));
connect(ui.pbWithThread, SIGNAL(clicked()), this, SLOT(pbWithThreadClicked()));
}



void Qt_Thread::pbWithoutThreadClicked()
{
b = 0;

while (b < 1000000){
b++;
ui.lbb->setText( QString("%1").arg(b) );
}
}

void Qt_Thread::pbWithThreadClicked()
{
MyThread m;
m.run();
}

void Qt_Thread::setb(int b)
{
ui.lbb->setText( QString("%1").arg(b) );
}

void Qt_Thread::pbIncAClicked()
{
a++;
ui.lba->setText( QString("%1").arg(a) );
}


Qt_Thread::~Qt_Thread()
{

}

MarekR22
25th November 2010, 18:13
Hi,

Best approach to solve this problem, is:
1. create QObject which do calculation in some slot:


class MyCalulations : public QObject
{
Q_OBJECT

public:
MyCalulations(QObject* parent);

public slots:
void doHeavyCalculation();

signals:
void calculationCompleted();
};


MyCalulations::MyCalulationsQObject* parent) :
QObject(parent)
{}

void MyCalulations::doHeavyCalculation()
{
// do some have calculation code here:
.....


emit calculationCompleted(); // report end of calculation
}

2. Then in your widget/main window:


MyWidget::MyWidget(QWidget* parent) :
QWidget(parent)
{
ui->constuct_ui();

QThread* thread = new QThread(this);
MyCalulations* calc = new MyCalulations(this);
calc->moveToThread(thread);

connect(ui->buttonCalc, SIGNAL(click()),
calc, SLOT(doHeavyCalculation()));
connect(calc, SIGNAL(calculationCompleted()),
this, SLOT(doSomethingWhenItIsDone()));
}


So as you can see it is easy and Qt will do lots of stuff for you.

SixDegrees
25th November 2010, 18:16
processEvents() is fairly slow. You only need to call it often enough to keep your GUI responsive; calling it on every loop iteration adds a lot of unnecessary calls. Something like


if ( !(loopIter % 10000) )
processEvents();

will likely work. Adjust the divisor as required to maintain interactivity.

Spawning the work performed within the loop in a thread is another option, as you note, but I'd try the approach outlined here first because it's simple. If it doesn't work, then you'll have to explore threading. Note that you can't directly updated the GUI from within a thread; you'll need to work out some sort of signalling mechanism if GUI updates are required.

squidge
25th November 2010, 18:19
You really need to learn how threads work. Your subclassing QThread (which isn't necessary in this case), creating a method called 'run' and then calling that function. So it's just acting like any another class. Instead of calling run() try calling start(). You may also need to use moveToThread() to ensure your slots get called in the current thread (otherwise they'll be called in your main thread)

Also, don't do UI calls in a thread. That's just asking for trouble. Do all UI in your main thread.

Nokia has a fantastic archive of presentation material. Maybe you would find it beneficial to watch the Threading in Qt video/presentation.

Matty23
25th November 2010, 21:04
Thanks for all your help. I am very new to this chapter and it seems like I have to learn a lot about it.

My modified source seems to work quite well and it looks like this:

#include <QThread>
#include <QMessageBox>
#include "qt_thread.h"

int a = 0;
int b = 0;

QThread *t;
MyCalc *c;





MyCalc::MyCalc(QObject* parent) :
QObject(parent)
{

}

void MyCalc::myLoop()
{
b = 0;
float tmp = 0.0;


while (b < 300000000){
tmp = 6.5192412518215212 * (tmp+1) / 2.6315211121893 + b * b;

b++;

if (! (b % 100000000) ){
emit incrementedB();
}
tmp = 0.0;

}

emit finishedLoop(); // report end of calculation
}



void Qt_Thread::updateMyB()
{
ui.lbb->setText( QString("%1").arg(b) );
}



Qt_Thread::Qt_Thread(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);

t = new QThread();
t->start();

c = new MyCalc();
c->moveToThread(t);

connect(ui.pbWithThread, SIGNAL(clicked()), c, SLOT(myLoop()));
connect(c, SIGNAL(finishedLoop()),this, SLOT(afterLoop()));
connect(c, SIGNAL(incrementedB()), this, SLOT(updateMyB()) );


connect(ui.pbIncA, SIGNAL(clicked()), this, SLOT(pbIncAClicked()));
connect(ui.pbWithoutThread, SIGNAL(clicked()), this, SLOT(pbWithoutThreadClicked()));
}



void Qt_Thread::pbWithoutThreadClicked()
{
b = 0;
float tmp = 0.0;

while (b < 300000000){
tmp = 6.5192412518215212 * (tmp+1) / 2.6315211121893 + b * b;
b++;
if (! (b % 100000000) ){
ui.lbb->setText( QString("%1").arg(b) );
qApp->processEvents();
}
tmp = 0.0;

}

ui.lbb->setText( "Fertig :)" );
}

void Qt_Thread::pbWithThreadClicked()
{

}

void Qt_Thread::setb(const int b)
{
ui.lbb->setText( QString("%1").arg(b) );
}

void Qt_Thread::pbIncAClicked()
{
a++;
ui.lba->setText( QString("%1").arg(a) );
}

void Qt_Thread::afterLoop()
{
ui.lbb->setText("Fertig! :)");
}


Qt_Thread::~Qt_Thread()
{

}


Thank you all!