MichaelW
MichaelW

Reputation: 1464

Qt synchronous method

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

Answers (2)

aep
aep

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

mcchu
mcchu

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

Related Questions