QCoreApplication ignores quit signal and hangs

2019-07-15 06:48发布

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();
}

标签: c++ qt qtcore
2条回答
\"骚年 ilove
2楼-- · 2019-07-15 07:34

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

QObject::connect(&obj, SIGNAL(done()), &app, SLOT(quit()), Qt::QueuedConnection);
查看更多
Root(大扎)
3楼-- · 2019-07-15 07:38

QCoreApplication::quit() is a no-op before the event loop is started, so you can't invoke it directly. The semantics of the quit() 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 precedes app.exec() in the body of main(). Thus, if you call quit() before app.exec(), there's no event loop to quit, and quit() does nothing.

In your code, as soon as obj.foo() emits the done() signal, the app.quit() gets invoked. The app.quit() method is in fact called from the moc-generated implementation of the done() 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 queues QMetaCallEvent for queued connections. So, for our purpose here, the obj.foo() line is equivalent to directly invoking app.quit(). Since you do it before app.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 a QueuedConnection 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. The quit() slot is not invoked direcly, just a data structure is posted to the event queue. But that data structure has a meaning to QObject::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, the quit() slot is called, and the application is quit since app.exec() returns as it was running an event loop, but was told to quit it. The QMetaCallEvent encapsulates a function call. It is akin to a closure.

All you need to do is to change your connection to a queued one.

// QT 5 syntax
connect(&obj, &hang::done, &app, &app::quit, Qt::QueuedConnection);
// QT 4 syntax
connect(obj, SIGNAL(done()), &app, SLOT(quit()), Qt::QueuedConnection);
查看更多
登录 后发表回答