QCoreApplication忽略退出信号并挂起

fgi*_*eau 2 c++ qt qtcore

当在启动事件循环之前同步触发信号QCoreApplication :: quit()时,将忽略该信号并且应用程序将永久挂起.但是,从QTimer触发,应用程序正确退出.在启动exec循环之前启动可以立即返回的任务的正确方法是什么?

以下是重现此行为的最小代码:

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
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

Rei*_*ica 8

QCoreApplication::quit()在事件循环启动之前是无操作,因此您无法直接调用它.quit()字面意思是该方法的语义:退出运行事件循环.显然,如果没有运行事件循环,则不会发生任何事情.

调用app.exec()启动主线程的事件循环.在那次调用之前,事件循环不会运行 - 你的其他一些代码正在运行 - 无论app.exec()是在正文中还是以前的代码main().因此,如果您quit()之前调用过app.exec(),则没有事件循环退出,并且quit()什么都不做.

在您的代码中,只要obj.foo()发出done()信号,app.quit()就会调用gets.app.quit()事实上,该方法是从moc生成的done()信号方法实现中调用的.这是因为连接是直接类型的.信号只是一个机器生成的方法,它从其体内调用所有直接连接,并排队QMetaCallEvent排队连接.因此,对于我们这里的目的,该obj.foo()行等同于直接调用app.quit().由于您在app.exec()运行之前执行此操作,因此没有任何操作,因为没有事件循环可以退出.

相反,你应该排队只在事件循环开始运行时才会被拾取的"东西",然后让循环退出.执行此操作的一种方法是将事件发布到将使其退出的应用程序对象.

碰巧有一个内部QMetaCallEvent封装了插槽调用.每当a QueuedConnection用于信号 - 时隙连接时,该事件的排队由信号完成.

因此,当您的信号触发时,QMetaCallEventgui线程事件循环的事件队列中内部排队.的quit()时隙未被direcly调用,只是一个数据结构发布到事件队列.但是这个数据结构有意义QObject::event()- 它会在遇到事件时重新构建调用.

因此,一旦事件循环开始执行app.exec(),就会拾取事件,quit()调用插槽,并且应用程序退出,因为app.exec()它在运行事件循环时返回,但被告知要退出它.该QMetaCallEvent封装函数调用.它类似于封闭.

您需要做的就是将连接更改为排队的连接.

// QT 5 syntax
connect(&obj, &hang::done, &app, &app::quit, Qt::QueuedConnection);
// QT 4 syntax
connect(obj, SIGNAL(done()), &app, SLOT(quit()), Qt::QueuedConnection);
Run Code Online (Sandbox Code Playgroud)