2

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

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

挂起.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

挂起.cpp

#include "hang.h"
#include <iostream>

hang::hang(QObject *parent) :
    QObject(parent)
{
}

void hang::foo()
{
    std::cout << "foo emit done()" << std::endl;
    emit done();
}

主文件

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

2 回答 2

12

QCoreApplication::quit()在事件循环开始之前是一个空操作,所以你不能直接调用它。从字面上看,该quit()方法的语义是:退出正在运行的事件循环。显然,如果没有事件循环正在运行,则什么也不会发生。

调用app.exec()启动主线程的事件循环。app.exec()在该调用之前,事件循环不会运行 - 您的其他一些代码正在运行 - 无论main(). 因此,如果您调用quit()before app.exec(),则没有要退出的事件循环,并且quit()什么也不做。

在您的代码中,一旦obj.foo()发出done()信号,app.quit()就会调用。该app.quit()方法实际上是从信号方法的 moc 生成的实现中调用的done()。这是因为连接是直接类型的。信号只是一种机器生成的方法,它从其主体中调用所有直接连接,并QMetaCallEvent为排队的连接排队。因此,就我们这里的目的而言,该obj.foo()行相当于直接调用app.quit(). 由于您在app.exec()运行之前执行此操作,因此它什么也不做,因为没有要退出的事件循环。

相反,您应该将仅在事件循环开始运行后才会被拾取的“东西”排队,然后让循环退出。一种方法是向应用程序对象发布一个事件,使其退出。

碰巧有一个QMetaCallEvent封装槽调用的内部。QueuedConnection每当 a用于信号槽连接时,此事件的排队都是由信号完成的。

因此,当您的信号触发时,QMetaCallEventgui 线程的事件循环的事件队列中有一个内部排队。quit()插槽不是直接调用的,只是将一个数据结构发布到事件队列中。但是这个数据结构有一个意义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);
于 2013-10-03T01:01:04.227 回答
2

Qt 文档指出,默认情况下,当发送者和接收者是同一个线程时,会直接调用它。在这种情况下,事件循环不会启动并且无法响应事件。解决方案是指定在 QObject::connect 中对事件进行排队

QObject::connect(&obj, SIGNAL(done()), &app, SLOT(quit()), Qt::QueuedConnection);
于 2013-10-02T16:36:14.873 回答