When the signal QCoreApplication::quit() is triggered synchronously before the event loop is started, the signal is ignored and the application hang forever. However, is triggered from QTimer, the application quits correctly. What would be the proper way to start a task that can return immediately before the exec loop is started?
Here is a minimal code to reproduce this behavior:
hang.h
#ifndef HANG_H
#define HANG_H
#include <QObject>
class hang : public QObject
{
Q_OBJECT
public:
explicit hang(QObject *parent = 0);
signals:
void done();
public slots:
void foo();
};
#endif // HANG_H
hang.cpp
#include "hang.h"
#include <iostream>
hang::hang(QObject *parent) :
QObject(parent)
{
}
void hang::foo()
{
std::cout << "foo emit done()" << std::endl;
emit done();
}
main.cpp
#include <QCoreApplication>
#include <QTimer>
#include <hang.h>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
hang obj;
QObject::connect(&obj, SIGNAL(done()), &app, SLOT(quit()));
// obj.foo() does emit done(), but app hang on exec
obj.foo();
// If done() signal is triggered from the timer, app quits correctly
//QTimer::singleShot(0, &obj, SLOT(foo()));
return app.exec();
}
Qt documentation states that by default when the sender and receiver is the same thread, then it is invoked directly. In that case, the event loop is not started and can't respond to the event. The solution is to specify to queue the event in the QObject::connect
QCoreApplication::quit()
is a no-op before the event loop is started, so you can't invoke it directly. The semantics of thequit()
method are, literally: Quit the running event loop. Obviously, if no event loop is running, nothing happens.Invoking
app.exec()
starts the main thread's event loop. Before that call, the event loop doesn't run - some other code of yours is running - whatever precedesapp.exec()
in the body ofmain()
. Thus, if you callquit()
beforeapp.exec()
, there's no event loop to quit, andquit()
does nothing.In your code, as soon as
obj.foo()
emits thedone()
signal, theapp.quit()
gets invoked. Theapp.quit()
method is in fact called from the moc-generated implementation of thedone()
signal method. This is because the connection is of a direct type. A signal is just a machine-generated method that invokes all direct connections from within its body, and queuesQMetaCallEvent
for queued connections. So, for our purpose here, theobj.foo()
line is equivalent to directly invokingapp.quit()
. Since you do it beforeapp.exec()
is running, it does nothing as there's no event loop to exit.Instead, you should queue "something" that will only be picked up once the event loop starts running and make the loop quit then. One way of doing it is to post an event to the application object that will make it quit.
It so happens that there is an internal
QMetaCallEvent
that encapsulates slot calls. The queueing of this event is done by the signal whenever aQueuedConnection
is used for the signal-slot connection.So, when your signal fires, there's a
QMetaCallEvent
internally queued in the event queue of the gui thread's event loop. Thequit()
slot is not invoked direcly, just a data structure is posted to the event queue. But that data structure has a meaning toQObject::event()
- it will reconstitute the call when it encounters the event.So, once the event loop starts executing in
app.exec()
, the event is picked up, thequit()
slot is called, and the application is quit sinceapp.exec()
returns as it was running an event loop, but was told to quit it. TheQMetaCallEvent
encapsulates a function call. It is akin to a closure.All you need to do is to change your connection to a queued one.