当在启动事件循环之前同步触发信号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
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();
}
答案 0 :(得分:8)
QCoreApplication::quit()
是一个无操作,因此您无法直接调用它。 quit()
方法的语义字面意思是:退出正在运行的事件循环。显然,如果没有运行事件循环,则不会发生任何事情。
调用app.exec()
启动主线程的事件循环。在该调用之前,事件循环不会运行 - 您的其他一些代码正在运行 - app.exec()
正文中main()
之前的任何代码。因此,如果您在quit()
之前致电app.exec()
,则无法退出事件循环,quit()
不执行任何操作。
在您的代码中,只要obj.foo()
发出done()
信号,app.quit()
就会被调用。 app.quit()
方法实际上是从done()
信号方法的moc生成实现中调用的。这是因为连接是直接类型的。信号只是一个机器生成的方法,它从其体内调用所有直接连接,并为排队连接排队QMetaCallEvent
。因此,出于我们的目的,obj.foo()
行等同于直接调用app.quit()
。由于您在app.exec()
运行之前执行此操作,因此没有任何操作,因为没有事件循环可以退出。
相反,你应该排队只在事件循环开始运行时才会被选中的“东西”并使循环退出然后。一种方法是将事件发布到将使其退出的应用程序对象。
恰好有一个内部QMetaCallEvent
封装了插槽调用。只要QueuedConnection
用于信号插槽连接,就会通过信号完成此事件的排队。
因此,当你的信号触发时,gui线程的事件循环的事件队列内部有一个QMetaCallEvent
。不会直接调用quit()
槽,只会将数据结构发布到事件队列中。但是这个数据结构对QObject::event()
有意义 - 它会在遇到事件时重新构建调用。
因此,一旦事件循环开始在app.exec()
中执行,就会拾取事件,调用quit()
插槽,并且应用程序退出,因为app.exec()
在运行时返回一个事件循环,但被告知退出它。 QMetaCallEvent
封装了函数调用。它类似于closure。
您需要做的就是将您的连接更改为排队的连接。
// QT 5 syntax
connect(&obj, &hang::done, &app, &app::quit, Qt::QueuedConnection);
// QT 4 syntax
connect(obj, SIGNAL(done()), &app, SLOT(quit()), Qt::QueuedConnection);
答案 1 :(得分:2)
Qt文档声明默认情况下,当发送方和接收方是同一个线程时,它会直接调用。在这种情况下,事件循环不会启动,也无法响应事件。解决方案是指定在QObject :: connect
中对事件进行排队QObject::connect(&obj, SIGNAL(done()), &app, SLOT(quit()), Qt::QueuedConnection);