Reputation: 1464
I'm a pure Qt beginner and currently want to understand its basic concepts and how to use them in the right way. My question might appear as somehow "ragged" and I want to apologize for that in advance. This having said, the question is:
I want to implement a method, which "blocks" until the handling is finished (for what reason ever...). In order to do the internals, I have to use an asynchronous class, emmiting a signal when it has finished its job in the background. I want to use that in a blocking way:
QEventLoop myLoop;
workerClass x;
QObject::connect(x, SIGNAL(finished()), &myLoop, SLOT(quit()));
/* x is asnchrounous and will emit "finished" when ist job is done */
x.doTheJob();
myLoop.exec();
Will that work? According to some other postings around - it should. But what is going on exactly?
When a new instance of QEventLoop is just created, does it automatically mean, that signals emitted from myLoop are automatically handled within that new handler? Moreover, the loop starts some while after having started the job. What happens, when it emits already "finished", before myLopp is started?
I have been told by a collegue, that I could use
QEventLoop myLoop;
workerClass x;
QObject::connect(x, SIGNAL(finished()), &myLoop, SLOT(quit()));
/* doTheJob is a slot now */
QMetaObject::invokeMethod(x, "doTheJob", Qt::QueuedConnection);
myLoop.exec();
I understand, that in this case doTheJob is called from the event loop. But from which event loop? invokeMethod() is not told to invoke the method in a particular event loop (myLoop), so why should the event posted to myLoop instead into the "outer" loop? In fact, myLoop is not even running at that time...
I hope I was able to transport my question to be understandable by experts.
Many Thanks, Michael
Upvotes: 4
Views: 2658
Reputation: 806
on your first part of the question: yes that will work.
There's actually a class for it in Qxt.
in order to understand how that works, you need to understand how event handling in general works, and this particular hack, while being a bad idea in generall, is a great way to learn it.
let's imaging a simplified call stack with just one mainloop:
6 myslot()
5 magic
4 someEvent.emit
3 eventloop.exec
2 qapplication.exec
1 main
as long as you don't return from myslot(), the "magic" cannot call any other slots that are connected to the same signal, and no other signal can be processed.
Now, with the nested eventloop "signalwaiter" hack:
10 myslot2()
9 magic
8 someEvent2.emit
7 eventloop.exec
6 myslot()
5 magic
4 someEvent.emit
3 eventloop.exec
2 qapplication.exec
1 main
we still cannot continue processing the event that we're blocking, but we can process new events, such as that signal you're connecting to quit.
This is not re-entrant, and generally a very bad idea. You may well end up with very hard to debug code, because you might be calling slots from a stack you don't expect.
The way to avoid the fire-before-exec problem, is to connect your signal to a slot that sets a flag. if the flag is set before exec, just don't exec.
However, for the sake of explanation, lets's see how your method works (it does work):
when you use Qt::QueuedConnection, a signal emission is not a stacked call anymore. It looks more like this:
5 eventloop.putQueue
4 someSignal.emit
3 eventloop.exec
2 qapplication.exec
1 main
then emit returns
3 eventloop.exec
2 qapplication.exec
1 main
and eventloop puts your slot on the stack
5 myslot()
4 eventloop.pop
3 eventloop.exec
2 qapplication.exec
1 main
So at the time your slot is called, your signal has already been removed from the stack and all local variables are gone.
this is vastly different than DirectConnection, and important to understand for other stuff like QObject::deleteLater()
To answer the question which eventloop executes your queue: the one on top of the stack.
Upvotes: 1
Reputation: 3369
The key is QObject::connect
:
QObject::connect(x, SIGNAL(finished()), &myLoop, SLOT(quit()));
This function says when finished()
is emitted, myLoop.quit()
should be called just after the event occurs. So in your example,
<--------thread A--------> | <--------thread B-------->
QEventLoop myLoop;
workerClass x;
x.doTheJob(); starts thread B
sleep in exec() workerClass::JobA();
sleeping..... workerClass::JobB();
sleeping..... workerClass::JobC();
sleeping..... workerClass::JobD();
sleeping..... workerClass::finished();
sleeping.....
wake up by quit();
myLoop.quit
must be called after thread A is sleeping, otherwise thread A may sleep forever because quit
may be called before sleeping. You have to find a way to specify this. But how? Take a look at QObject::connect, the last argument is
Qt::ConnectionType type = Qt::AutoConnection
And about Qt::AutoConnection,
(Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used.
The signal is emitted in thread B and the receiver myLoop
is in thread A, that means you are using Qt::QueuedConnection:
The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
The slot myLoop.quit
is invoked when control returns to the event loop, myLoop.exec
. In other words, quit
is invoked only when exec
is running. This is exactly what we want and everything works fine. Therefore, your first example always runs correctly because the signal and slot are connecting together using Qt::QueuedConnection
(from default value Qt::AutoConnection
).
Your second example works fine because QObject::connect
is the same, too.
If you change it to Qt::DirectConnection
, the story is different. quit
is called in thread B, it is possible that quit
is called before exec
and thread A is going to sleep forever. So in this scenario you should never use Qt::DirectConnection
.
The story is about QObject::connect
and threads. QMetaObject::invokeMethod
is not related to this, it simply calls a function when the thread executes an exec
function.
Upvotes: 2