4

...静的クラスおよび非メインスレッドから呼び出されます。つまり、静的メンバーとして別の静的クラス「tobj」を持つクラス「sapp」があります。静的な順序の初期化の失敗を回避するために、tobjはsappのメソッド内で宣言され、sappのメソッドはtobjのインスタンスのポインターを返します。私の問題は、tobjにはコンストラクターで開始する必要のあるタイマーがあり、tobjは非メインスレッドによって作成される可能性があることです。QTimerは、メインスレッド以外のスレッド(またはイベントループがないと思われるスレッド)で起動することはできません。そのため、スレッドの問題を回避するためにQMetaObject :: invokeMethod + Qt::QueuedConnectionを介してQTimer::startを呼び出しますが、機能しません。QTimer::startが呼び出されることはありません。問題を少し調べたところ、QTimerが呼び出されなかったため、QTimer::startが呼び出されなかったようです。■親(この場合はtobj)は静的として宣言されます。tobjを非静的メンバーとして宣言すると、すべてが正常に機能します。

Qtの内部をよく理解していません。これはバグなのか、何か間違ったことをしているのでしょうか。

コードは次のとおりです。

class tobj : public QObject
{
    Q_OBJECT

    QTimer timer;
private slots:
        void timeout();

public:
    tobj();
};

class sapp : public QObject
{
    Q_OBJECT

public:
    static tobj* f();
};


void tobj::timeout()
{
    qDebug() << "hi";
}

tobj::tobj()
{
    connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
    timer.setInterval(500);
    qDebug() << QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection); // returns true, but never invoked.
}

tobj* sapp::f()
{
    static tobj ff;
    return &ff;
}

これは、1つのヘッダーと1つのcppファイルで構成されるテストプロジェクトへのリンクですhttp://dl.dropbox.com/u/3055964/untitled.zip

Qt4.8.0とMSVC2010でテストしています。

どうもありがとうございました、あなたの助けは大歓迎です。

4

1 に答える 1

5

あなたはそれをやり過ぎていると思います。Qtの美しさは、あなたがやろうとしていることがとても簡単であるという事実にあります。

QTimerあなたは、コールバックを実行するために、実行中のスレッドを何らかの形で魔法のように中断すると考えているようです。これはQtでは決して当てはまりません。Qtでは、すべてのイベントとシグナルがスレッドで実行されているイベントループに配信され、そのイベントループがそれらをQObjectsにディスパッチします。したがって、スレッド内では、Qtのイベントおよびシグナル/スロットフレームワークによる同時実行の危険はありません。また、スレッド間で通信するためにQtのシグナルスロットとイベントメカニズムに依存している限り、各スレッド内でシリアル化されるため、他のアクセス制御プリミティブを使用する必要はありません。

したがって、問題は、スレッドでイベントループを実行しないことです。そのため、タイムアウトイベントが取得されて、timeout()信号に接続したスロットにディスパッチされることはありません。

AQTimerは、開始するスレッドがタイマーが存在するスレッドである限り、任意のスレッドで作成および開始できますQObject。QObjectは、を使用して別のスレッドに移動しない限り、作成したスレッドに属しますQObject::moveToThread(QThread*)

タイマーを開始するためにを使用するinvokeMethod必要はまったくありません。結局のところ、タイマーはそれが存在するのと同じスレッドから開始しています。キューに入れられた信号スロット接続は、名前が示すように、信号をキューに入れます。具体的には、シグナルを送信するQMetaCallEventと、レシーバースロットが存在するQObjectのキューに入れられます。これを取得して呼び出しを実行するには、スロットオブジェクトのスレッドでイベントループを実行する必要があります。スレッド内の何かを呼び出してそのキューを空にすることは決してないため、timeout()スロットを呼び出すことはできません。

静的メンバーの初期化の大失敗については main()、メインスレッドにあるQObject内またはQObject内にTオブジェクト全体を自由に構築し、それを新しいスレッドに移動できます。これは、QtConcurrentを使用しない場合の方法です。 。QtConcurrentを使用する場合、実行可能な関数は任意の数のQObjectを構築および破棄できます。

コードを修正するには、ラムダはイベントループをスピンする必要があります。

[] () {
    sapp s;
    s.f();
    QEventLoop l;
    l.exec();
}

以下に、Qtで慣例的に行われる方法のSSCCEの例を添付します。QtConcurrentを使用したくない場合は、2つの変更があります。スレッドのイベントループのqApp->exit()直後に変更する必要があります。それ以外の場合は、終了しません(どのように認識しますか?)。また、関数を終了する前にスレッドをオンにする必要があります。まだ実行中の関数を破棄するのは悪いスタイルです。exit()a.exec()wait()main()QThreads

これらのexit()関数は、イベントループに終了を通知するだけであり、フラグを設定するものと考えてください。実際には何も終了しないため、CスタイルのAPIとは異なりますexit()s

QTimeraはQObjectそれ自体であることに注意してください。パフォーマンス上の理由から、タイマーが頻繁に起動する場合は、QBasicTimerによって返されるタイマーIDの単純なラッパーであるを使用する方がはるかに安価QObject::startTimer()です。次に、を再実装しQObject::timerEvent()ます。このようにして、シグナルスロット呼び出しのオーバーヘッドを回避できます。経験則として、直接(キューに入れられていない)シグナルスロット呼び出しは、2つの1000文字のQStringを連結するのと同じくらいのコストがかかります。私のベンチマークを参照してください。

補足:短い例を投稿する場合は、コード全体を直接投稿する方が簡単な場合があります。これにより、すべてが1つのファイルに含まれている限り、将来失われることはありません。3つのファイルに100行のサンプルコードが散らばっているのは逆効果です。以下を参照してください。キーは#include "filename.moc"の最後にありfilename.cppます。また、Javaスタイルで、メソッドを一度に定義および宣言するのにも役立ちます。短くてわかりやすいという名目ですべて。結局のところ、これは一例です。

//main.cpp
#include <QtCore/QTimer>
#include <QtCore/QDebug>
#include <QtCore>
#include <QtCore/QCoreApplication>

class Class : public QObject
{
    Q_OBJECT
    QTimer timer;
    int n;
private slots:
    void timeout() {
        qDebug() << "hi";
        if (! --n) {
            QThread::currentThread()->exit();
        }
    }
public:
    Class() : n(5) {
        connect(&timer, SIGNAL(timeout()), SLOT(timeout()));
        timer.start(500);
    }
};

void fun()
{
    Class c;
    QEventLoop loop;
    loop.exec();
    qApp->exit();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QtConcurrent::run(&fun);
    return a.exec();
}

#include "main.moc"
于 2012-06-04T12:21:28.850 に答える