PDA

View Full Version : QThread, moveToThread question



Anenja
23rd February 2013, 23:49
Hi together,

I have some problems with understanding the behaviour of my following code...
I tryed to udnerstand the Threading in Qt, so i found a nice youtube vid which i would like to share with you - its from Dario Freddi http://www.youtube.com/watch?v=MMFhc2jXzgw


I Wrote a simple application to try movetothread methode, just 2 Workers and a QWidget class with a button. on button clicked worker 1 starts -> in its cTor it creates worker2 and waits of the finish signal from worker2. But some code say more than words so.
i can post the whole simple project, but i dont know if its ok, so i just post the some few lines of code.

QWidget:

void Widget::on_btTest_clicked()
{
QTime time;
time.start();
worker1 *worker = new worker1;
worker->doWork1();
qDebug() << time.elapsed() << "mseconds" << "current time:" << time.currentTime().toString("hh:mm:ss:z")<< "worker1 result" << worker->getRes();
}

Worker1:

worker1::worker1(QObject *parent) :
QObject(parent),
m_classThread(new QThread),
m_resultString("Hallo")
{
moveToThread(m_classThread);
m_worker2 = new Worker2;
//breaks the own event loop if worker 2 is finished
connect(m_worker2, SIGNAL(workDone()), &wait, SLOT(quit()));
connect(this, SIGNAL(startNow()), m_worker2, SLOT(doWork2()));
m_classThread->start();
}

void worker1::doWork1()
{
//starts worker2 und wartet auf sein finished
qDebug() << Q_FUNC_INFO;
emit startNow();
wait.exec();
}

QString worker1::getRes()
{
return m_resultString;
}

Worker2:


Worker2::Worker2(QObject *parent) :
QObject(parent),
m_classThread(new QThread),
m_resultString("123")
{
moveToThread(m_classThread);
tima.setInterval(5000);
tima.start();
m_classThread->start();
}

void Worker2::doWork2()
{
qDebug() << Q_FUNC_INFO;
// create a event loop for the timer to be finished
QEventLoop wait;
connect(&tima, SIGNAL(timeout()), &wait, SLOT(quit()));
wait.exec();
emit workDone();

}



Results in the Debug frame:


void worker1::doWork1()
void Worker2::doWork2()
void worker1::doWork1()
void Worker2::doWork2()
5003 mseconds current time: "15:35:22:36" WIDGET: worker one "Hallo"
7204 mseconds current time: "15:35:22:36" WIDGET: worker one "Hallo"


So here is my missunderstood/question -> if i click the button twice with a simple delay of 2 seconds, why get i the results at the same time? Why i dont get the debug msg of the first click on the button from(worker.getRes() (in the widget class) not 2 seconds earlier than the msg of the 2nd click?

If I may suggest, then I would argue that the problem is that the queue for the widget/main thread queue is accessed when the 2nd click -> workers are finished? So the first debug msg got queued at the mainthread and fired when the last work is done? But i dont get it exactly whats the problem here.

It would be very nice if s.o can explain me the above behaviour. An it would be really nice , to see how to solve this "problem". thanks!
(if u want me to post the project as attachement - just tell me....)

TIA Anenja!

Lykurg
24th February 2013, 06:47
Hi, in worker1::doWork1(): what is "wait"?

Further, when you use moveToThread, you have to call slots by using signals. If you call them via "->" the function is executed in the same thread as the caller. (In the video he is connecting the QThread::started() with a moved object slot.). Also if you move Worker2 to a Thread, there is - I think, since I am also no thread expert - no need for an event loop inside.

anda_skoa
24th February 2013, 10:17
Also if you move Worker2 to a Thread, there is - I think, since I am also no thread expert - no need for an event loop inside.

The base implementation of QThread::run() already runs an event loop (see QThread::exec(), QThread::quit()), but a nested event loop is not wrong either. Depends on whether you want to keep the thread itself running.

I agree that worker1->doWork1() looks wrong since it totally bypasses the first thread. One would expect that the widget's code connects to some sort of result signal and then starts the first thread.

Cheers,
_

Anenja
24th February 2013, 11:42
Hi, in worker1::doWork1(): what is "wait"?
Thanks for your help!
wait is just an eventLoop.
I Dont subclass qthread so i need a own eventloop if i want to wait for s.th at this point within this thread.


I agree that worker1->doWork1() looks wrong since it totally bypasses the first thread
Thanks for your help!
Ok i understand this. Still if i connect doWork1() to the thread started() signal, in its cTor it dont fix the behaviour.


What i think what happens:
I create a worker object on bt_clicked() in the MainThread, then in workers cTor the object is moved to it´s own thread, then in the cTor of worker1 I create worker2 (in the mainthread - cause the thread of worker 1 hasn´t started yet, then in workers2 cTor the object is moved to its own thread. So, worker1 life in its thread an worker 2 does. worker 1 waits in it´s own QEventloop for worker2 to be finished.
the debug msg of the threadID´s confirm the things i wrote above.

i fix the code so only signal/slots will be used...

//breaks the own event loop if worker 2 is finished
connect(m_worker2, SIGNAL(workDone()), &wait, SLOT(quit()));
// signal startNow is emitted in doWork1()
connect(this, SIGNAL(startNow()), m_worker2, SLOT(doWork2()));
connect(m_classThread, SIGNAL(started()), this, SLOT(doWork1()));
m_classThread->start();
wait.exec();

QWidget:


....
worker1 *worker = new worker1;
qDebug() << "this debug msg will executed if worker1 is finished ";
....


I really try to solve this and understand it - but i dont know if i am right here.
I click the button and the eventqueue of worker1 blocks the go on in the next line of code (in slot bt_clicked()) - thats ok i can understand this, but if i click this button again why are the debug mgs fired at the same time together?
if I add an other button on the widget i can click this bt while the threads are running (on click just show a debug msg) and it is fired instantly.

I hope u can follow/understand me, my english is not really good -.-

debug console:


Worker1 created()
"another widget bt bt_clickedNew tho show just a debug msg"
Worker1 created()
"another widget bt bt_clickedNew tho show just a debug msg"
5002 mseconds current time: "12:40:14:355" WIDGET: worker one QThread(0x851cb90) "Hallo"
6987 mseconds current time: "12:40:14:355" WIDGET: worker one QThread(0x840ce58) "Hallo"




Thanks for your help!

amleto
24th February 2013, 16:14
why don't you use your debugger and some breakpoints to see what is happening?

Anenja
24th February 2013, 16:57
amleto

Re: QThread, moveToThread question
why don't you use your debugger and some breakpoints to see what is happening?


Thanks for your help.
Hi, i tryed to debug it, but i dont get anything useful information out of it :(

i really dont get the point where i did the mistake or the missunderstand happens. perhaps s.o can have a quick look at it -> i put the project as attachment.

Thanks again, really nice to get all ur help.!

amleto
24th February 2013, 18:08
If you dont get anything useful then add more. Learning how to debug is not optional for programmers.

What you have is frankly a mess of threads and timers in object that aren't moving in the same threads as their owners etc. Why don't you time how long worker 1 takes. It should be about 5s, right? Sometimes for me it is 5s, sometimes 7s. You should look into that if you are doing this just as problem solving. If you want to write some code for learning purpose, I suggest you throw away what you've done and write it properly.

Anenja
24th February 2013, 18:38
If you dont get anything useful then add more. Learning how to debug is not optional for programmers.

What you have is frankly a mess of threads and timers in object that aren't moving in the same threads as their owners etc. Why don't you time how long worker 1 takes. It should be about 5s, right? Sometimes for me it is 5s, sometimes 7s. You should look into that if you are doing this just as problem solving. If you want to write some code for learning purpose, I suggest you throw away what you've done and write it properly.

Thank you amleto.

i checked the timer variable and its thread and u are right :) the timer is in the mainthread home created and the object itself is moved to another thread.
i will debug it harder and try to rewrite it properly.

a question i got:
Worker *work = new Worker; //created in the current thread and ALL membervars are also created in this thread
If u now use moveToThread(t1) -> are the member vars still in the "old" thread, so is this a problem ?

TIA

amleto
24th February 2013, 19:35
all member vars of the instance that have not had their parent set to the instance will not move when the instance is moved.

Anenja
24th February 2013, 20:22
amleto

Re: QThread, moveToThread question
all member vars of the instance that have not had their parent set to the instance will not move when the instance is moved.


ok thanks i got this.

I also got my logical Problem in the above code. Debugger helps me to get it ;)

I Created an event loop in the cTor of Worker1 -> eventloop is created in the mainthread and is not moved (i think u cant move this loop cause it makes no sense or? - if i create a loop in a slot and moved the object in the ctor all is fine, the loop is created in the object thread <-parent is the object ....) The loop in the cTor stops the application to move on in the code (sry for this explanation but i really dont know how to explain this). what i mean is:


line 9: ....
line 10: Worker1 * worker = new Worker1; // at this point the cTor loop stops the further going to line 11 until the loop is finished
line 11: qDebug() << "....."


So if i am right, the eventloop which "lives" in the mainthread created in the cTor (line 10) is the reason why the qDebug msg are fired at the same time - if i click the button twice.
This i cause the eventloop of worker1 is started in the cTor, after 5 seconds worker2 quits this loop, but the new click on the button creates a new eventloop which again blocks the go on to line 11 (-> there can only be one eventloop in a thread right?) so after the new worker1 created by clicking the button again "reopens" the event queue (by creating a new in the mainthread) - if worker2 is now finished this eventloop is leaved and the qDebug msgs appear both at the same time.

It would be really nice if s.o can tell me if iam right - this is the result i got from debugging and this behaviour makes sense to me.

Thanks again and have a nice sunday ;)

amleto
24th February 2013, 23:33
there can be many event loops in a thread, and this is in fact your problem. If this didn't happen, you wouldn't be able to press the button for a second time since your worker1 is blocking the main thread. The only reason the gui remains responsive is because you start a second event loop in the main thread. When you get 'recursive' event loops, Qt does some managing of which signals and slots to process based on recursion level (I think) - this will be why your signal/slot from the first button press are delayed until after the second button press's signals have been processed.

Anenja
25th February 2013, 09:44
amleto

Re: QThread, moveToThread question
there can be many event loops in a thread, and this is in fact your problem. If this didn't happen, you wouldn't be able to press the button for a second time since your worker1 is blocking the main thread. The only reason the gui remains responsive is because you start a second event loop in the main thread. When you get 'recursive' event loops, Qt does some managing of which signals and slots to process based on recursion level (I think) - this will be why your signal/slot from the first button press are delayed until after the second button press's signals have been processed.


hi, thanks again for ur reply.

Ok i understand this ...

My Idea was to achieve s.th like this -


//create a worker and wait for its result
Line: 10: Worker1 *worker = new Worker1("params for some work");
//save the result of worker in the example String
Line 11: QString exampleString = worker->getRes();
// create a new Worker with the result of the first worker and start a new job with this result
Line 12: Worker1 *anotherTask = new Worker1(exampleString + "more nasty work");
line 13: qDebug() << "worker1 result" << worker->getRes() << " 2nd Worker res (based on the first worker res)" << anotherTask->getRes();


If this would work, i simply can move existing class to stop freezinig GUI and dont have to change the code at all, all i had to to is write a wrapper for the old class and replace the old class name with the new one in my code and i dont need do modify exissting codebrackets... But this was just an idea of using threads which probably doesnt work huh?
If i create worker1, and use the worker like in the code above i want to wait for its result - but i see there is probably no way to use this like the code above - i have to do this via signal/slots - right?
s.th like this:


//create a worker and wait for its result
..QThread t1;
Line 10: Worker1 *worker = new Worker1("params for some work");
worker->moveToThread(&t1)
// connect the worker with its finished signal, an start new worker with the result
connect(worker, SIGNAL(started), worker, SLOT(startWork());
Line 11: connect(worker, SIGNAL(finished()), this, SLOT(worker1FinishedSlotStartWorker2WiththeNewResu lt());
worker->start();
....

amleto
25th February 2013, 14:23
this would be better.


//create a worker and wait for its result
QThread t1 = new QThread();
Worker1 *worker = new Worker1("params for some work");
worker->moveToThread(t1)
// connect the worker with its finished signal, an start new worker with the result
connect(t1, SIGNAL(started), worker, SLOT(startWork());
connect(worker, SIGNAL(finished()), this, SLOT(worker1FinishedSlotStartWorker2WiththeNewResu lt());
t1->start();


I am concerned about how you will pass the new result to worker2 in this example, though.

Anenja
26th February 2013, 13:14
amleto

I am concerned about how you will pass the new result to worker2 in this example, though.

I just could give the finished signal a QString parameter and pass this to the destiny slot...

What i dont really understand is this:


QThread t1;
// All the membervars which was created in the cTor without the (this)parent wont be moved to the new thread right?
// that mean for example: membervar in the header file: int m_Test ;
// cTor (cpp file) -> m_Test = 5; or in the init list m_test(5)
QObject *obj = new QObject;
obj->moveToThread(&t1);


So if i create membervars and use movetoThread -> must i set the parent to all my membervars (all who inherit qobject) to the currentclass (this)?
Cause if i dont do this, the membervars are created in the mainthread for e.g and wont be moved to the new thread, so this maybe a problem right?
If i use Threads without subclassing, i have to take care "where" i run the methods of the object, that mean if i am not in the right thread i have to run the methods via connect().(they have to be a slot then)
I read s.th about QMetaObject::invoke.


//the current thread is not the workerThread so calling this way of the slot is safe?
QMetaObject::invokeMethod(pWorkersender, "slotName",Qt::AutoConnection); //instead pWorkersender->slotName()
Is this use of invokeMethod threadsafe? that mean can i call this way a slot without being afraid of getting problems?

Thanks again for all ur help, really great answers!

amleto
26th February 2013, 18:54
"So if i create membervars and use movetoThread -> must i set the parent to all my membervars (all who inherit qobject) to the currentclass (this)?"
Yes.

"Cause if i dont do this, the membervars are created in the mainthread for e.g and wont be moved to the new thread, so this maybe a problem right?"
Yes.

'invokeMethod' doesn't make something that isn't thread safe, thread safe. Using invokeMethod will make sure a slot is executed in its class instance's thread.

So yes, there is a difference between
QMetaObject::invokeMethod(pWorkersender, "slotName",Qt::AutoConnection); // slotname will be run in pworkersender's thread.
and
instead pWorkersender->slotName(); // slotname will be run in the current thread.

This is totally different to something being 'thread safe'.

Anenja
27th February 2013, 11:19
amleto
So yes, there is a difference between
QMetaObject::invokeMethod(pWorkersender, "slotName",Qt::AutoConnection); // slotname will be run in pworkersender's thread.
and
instead pWorkersender->slotName(); // slotname will be run in the current thread.

This is totally different to something being 'thread safe'.

Thanks again.

I will try to use your hints and make a simple application which is hopefully better than the first one ;)

Anenja

Anenja
6th March 2013, 11:01
To complete this, just a final simple example of using movetothread.

simple worker.h:

class Worker : public QObject {
Q_OBJECT

public:
Worker();
~Worker();

public slots:
void process();

signals:
void finished();

private:
};
worker.cpp

//
Worker::Worker() {
//the parent of the membervars should be (this). that they will be correctly moved to the new thread
}

void Worker::process() {
qDebug("Hello World!");
emit finished();
}


use in widget/main class


QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(process()));
//quits the event loop if a loop exists
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
//deletes the worker object when the object is finished
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
//deletes the thread when finished (of the thread) is emitted
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
//emits the process slot
thread->start();


I hope this use of movetothread is right, if not pls give me a hint...

Added after 1 7 minutes:

a nice post i found about threads, events and objects to make things more clear.

http://qt-project.org/wiki/Threads_Events_QObjects