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

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

2 に答える 2

12

QCoreApplication::quit()イベントループが開始される前はノーオペレーションなので、直接呼び出すことはできません。メソッドのセマンティクスquit()は、文字通り:実行中のイベント ループを終了します。明らかに、イベント ループが実行されていない場合、何も起こりません。

呼び出すapp.exec()と、メイン スレッドのイベント ループが開始されます。その呼び出しの前に、イベントループは実行されません - あなたの他のコードが実行されています -app.exec()の本体で先行するものは何でもmain(). したがって、quit()beforeを呼び出すとapp.exec()、終了するイベント ループがなく、quit()何もしません。

あなたのコードでは、 がシグナルをobj.foo()発するとすぐにが呼び出されます。このメソッドは実際には、moc によって生成されたsignal メソッドの実装から呼び出されます。これは、接続が直接型であるためです。シグナルは、その本体内からすべての直接接続を呼び出すマシン生成メソッドであり、キューに入れられた接続のキューです。したがって、ここでの目的のために、この行は を直接呼び出すことと同じです。実行前に行うため、終了するイベントループがないため、何もしません。done()app.quit()app.quit()done()QMetaCallEventobj.foo()app.quit()app.exec()

代わりに、イベント ループの実行が開始されたときにのみ取得される「何か」をキューに入れ、その後ループを終了させる必要があります。これを行う 1 つの方法は、アプリケーション オブジェクトを終了させるイベントをアプリケーション オブジェクトに送信することです。

QMetaCallEventスロット呼び出しをカプセル化する internal がある場合があります。このイベントのキューイングはQueuedConnection、シグナルスロット接続に a が使用されるたびに、シグナルによって行われます。

そのため、シグナルが発生するQMetaCallEventと、GUI スレッドのイベント ループのイベント キューに内部的にキューが作成されます。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 に答える