Making asynchronous calls work like synchronous calls
-
Dear Qt users,
I know this topic has been discussed all over the web. The usual suggestion is to use this: @QEventLoop loop;
loop.connect(object, SIGNAL(operationCompleted()), SLOT(quit()));
object->startOperation();
loop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents);@ However I was wondering: what happens if the signal is emitted before we call loop.exec()?
Could we stay stuck in the exec()? Particularly if the startOperation() calls processEvents?I have used this trick a few times in the past, but I'm now struggling with an asynchronous activex.
I can confirm that the operationCompleted() signal is emitted. However the loop never quits.
I've noticed that startOperation() takes a little time (500msec) and I don't know what happens there. -
Hi Julien M,
You could definitively stay stuck in the exec() if the "operationCompleted" signal is triggered. That is because signals are processed synchroneously in a single thread application, or if you call processEvent like you said.
To avoid this issue, you can you a QTimer::singleShot(0, object, SLOT ( startOperation() ) to trigger the start immediately after exec is called (after preceding events of course). Using "0" as duration posts an event that is processed the next event loop iteration.
-
Bonsoir Adrien.
Could you please detail the real reason for getting stuck in exec()?
As long as object->startOperation() does not handle an event loop, the emitted signal should be queued and processed by the exec() even if it emitted to early, right?Regarding your solution, again, could you detail it? Do you suggest this: @QEventLoop loop;
loop.connect(object, SIGNAL(operationCompleted()), SLOT(quit()));
loop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents);
QTimer::singleShot(0, object, SLOT(startOperation());@ Cause it sounds like the singleShot will never be reached! -
If your startOperation method contains an "emit operationCompleted()", then it will immediately trigger the quit slot of your event loop. When you write "emit signal()", in a single threaded application, all slots connected are called immediately, not posted.
To change this behaviour, you can use the fourth argument of the connect method: "connect (&sender, sig, &receiver, slot, Qt::QueuedConnection) ":http://doc.qt.digia.com/qt/qobject.html#connect
So in your case, it could look like...
@
myfunction::startProcessing
-> object::startOperation -> emit operationCompleted() -> QEventLoop::quit() // Does nothing
-> QEventLoop::exec() // Will never quit
@The same issue arises if you call processEvents() in you operationCompleted, even with an asynchronous connection.
The single shot timer should be used just before the exec(). That way, the event is posted, and the startOperation slot will be executed "from inside" the event loop.
@
QEventLoop loop;
loop.connect(object, SIGNAL(operationCompleted()), SLOT(quit()));
QTimer::singleShot(0, object, SLOT(startOperation());
loop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents);
@Bonne soirée :)
-
Ok, then is it right to say that the original piece of code from my first post is another example of "You are doing it wrong"?
If yes, why can we find this example here (last line of the table)
http://www.developer.nokia.com/Community/Wiki/TSQ001335_-_Asynchronous_operations_in_S60_and_Qt -
If you have a look at "this article":http://www.developer.nokia.com/Community/Wiki/How_to_wait_synchronously_for_a_Signal_in_Qt you will see that they do not recommend the approach for real applications. There are a number of problem that can arise if you are not 100% sure of what you are doing (race condition, recursive event loops,...).
Also I would recommend to always use a Timer for having the eventloop time out after a certain time.
@
QEventLoop loop;
QTimer timer;
timer.setInterval(YOUR_TIMEOUT_IN_MILLISECS);
timer.setSingleShot(true);
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
connect(object, SIGNAL(operationCompleted()), &loop, SLOT(quit()));
QTimer::singleShot(0, object, SLOT(startOperation()));
loop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents);
@ -
Would be nice to be able to determine if the QEventLoop has been stopped by the signal or by the timeout.
-
You might also be interested in the so-called "Delta Object Rules":http://delta.affinix.com/dor/ These rules, especially the second one, specifically describes the issue you run into where a finished() signal is send immediately after a request().
I find it a good read.
If you want to wait for one or more signals, you could also take a look at libQxt. There is a "QxtSignalWaiter":http://libqxt.bitbucket.org/doc/0.6/qxtsignalwaiter.html class that does all you need.
-
Andre ... you rock!