24

Qt Signal-Slot 通信に関するこのような記事をいくつか読んだ後でも、キュー接続に関する質問があります。

いくつかのスレッドが常に互いにシグナルを送信している場合、1 つthread_slowがイベント ループで遅いメソッドを実行していて、もうthread_fast1 つのスレッドがまだ実行されている間に複数のシグナルを送信する高速メソッドを実行しているとします。それは遅いメソッドです... .. からの遅いメソッドthread_slowがイベント ループに戻るとき、前に送信されたすべてのシグナルを処理しますthread_fastか、それとも最後のシグナルだけを処理しますか (すべてのシグナルは同じタイプです)。

すべてのシグナルを処理する場合、thread_slow 唯一のプロセスを最後のプロセスにする方法はありますか? (マルチスレッド アプリケーションで「最後の信号」を考慮すると曖昧になる可能性があります。簡単にするために、スレッドが最後の信号を要求する前の最後の信号を考えてみましょう。そのため、スレッドが最後の信号を探している間に送信される新しい信号は失われる可能性があります。 )。

(複数のスレッドからデータを受信する複数のスレッドがあり、それらに古いデータを処理させたくないので、これを求めています。送信された最後のデータのみです)

いくつかのテストを実行しましたが、Qt はすべてのシグナルを処理するようです。私は1つのスレッドを実行させました:

while(true)
{
    QThread::msleep(500);
    emit testQueue(test);
    test++;
}

別のスロットの場合は次のようになります。

void test::testQueue(int test)
{
    test.store(private_test.load() + test);
    emit testText(QString("Test Queue: ") + QString::number(private_test.load()));
}

そしてスレッドが実行されます:

while(true)
{
    QThread::msleep(3000);
    QCoreApplication::processEvents();
    private_test.store(private_test.load() + 1000);
}

私は 500 ミリ秒ごとに 1 つのスレッドから別のスレッドにシグナルを送信しており、もう 1 つのスレッドは 3000 ミリ秒 (3 秒) スリープしてから起動し、内部変数を 100 ずつインクリメントします。スロットが実行されるたびに、次のテキストを出力します。受け取った値 + 内部変数。私が持っている結果は、呼び出されるたび QCoreApplication::processEvents();にすべてのシグナルが実行されるということです....(以前のコードにバグが見つかったため、この部分を編集しました)

4

8 に答える 8

14

QCoreApplication QMetaCallEvent 圧縮

キューに入れられたすべてのスロット呼び出しはQMetaCallEvent、ターゲット オブジェクトへの a のポストで終了します。イベントには、送信者オブジェクト、シグナル ID、スロット インデックス、およびパッケージ化された呼び出しパラメーターが含まれます。Qt 5 では、通常、シグナル ID は によって返される値と等しくありませんQMetaObject::signalIndex()。これは、オブジェクトがシグナル メソッドのみを持ち、他のメソッドを持たないかのように計算されたインデックスです。

目的は、特定のタプル (送信側オブジェクト、送信側シグナル、受信側オブジェクト、受信側スロット) のイベント キューに一意の呼び出しが 1 つだけ存在するように、そのような呼び出しを圧縮することです。

これは、ソース オブジェクトまたはターゲット オブジェクトを変更する必要がなく、最小限のオーバーヘッドを維持しながら、これを行う唯一の適切な方法です。Qtが64ビットポインターアーキテクチャ用に構築されている場合、私の他の回答のイベントループ再帰メソッドには、イベントごとに1kバイトのオーダーで深刻なスタックオーバーヘッドがあります。

1 つ以上のイベントが既にポストされているオブジェクトに新しいイベントがポストされると、イベント キューにアクセスできます。このような場合、 をQCoreApplication::postEvent呼び出しますQCoreApplication::compressEventcompressEvent最初のイベントがオブジェクトにポストされたときは呼び出されません。このメソッドの再実装ではQMetaCallEvent、ターゲット オブジェクトにポストされたコンテンツのスロットへの呼び出しをチェックでき、廃止された複製を削除する必要があります。QMetaCallEventQPostEventおよびの定義を取得するには、プライベート Qt ヘッダーを含める必要がありQPostEventListます。

長所: 送信側オブジェクトも受信側オブジェクトも何も意識する必要はありません。シグナルとスロットは、Qt 5 のメソッド ポインター呼び出しを含め、そのままの状態で機能します。Qt 自体は、この方法でイベントを圧縮します。

短所: プライベート Qt ヘッダーを含め、QEvent::postedフラグを強制的にクリアする必要があります。

フラグをハッキングする代わりに、削除するイベントを別のリストにキューに入れ、ゼロ期間タイマーが起動されたときに呼び出しQEvent::postedの外で削除することができます。compressEventこれには、余分なイベント リストのオーバーヘッドがあり、各イベントの削除は、投稿されたイベント リストを反復処理します

その他のアプローチ

他の方法でそれを行うポイントは、Qt の内部構造を使用しないことです。

L1最初の制限は、私的に定義された の内容にアクセスできないことですQMetaCallEvent。次のように処理できます。

  1. ターゲットと同じシグネチャの信号とスロットを持つプロキシ オブジェクトは、ソース オブジェクトとターゲット オブジェクトの間に接続できます。

  2. プロキシ オブジェクトで を実行するQMetaCallEventと、呼び出しタイプ、呼び出されたスロット ID、および引数を抽出できます。

  3. シグナルスロット接続の代わりに、イベントを明示的にターゲット オブジェクトに送信できます。ターゲット オブジェクトまたはイベント フィルターは、イベントのデータからスロット呼び出しを明示的に再合成する必要があります。

  4. の代わりにカスタムcompressedConnect実装を使用できますQObject::connect。これにより、信号とスロットの詳細が完全に明らかになります。queued_activateプロキシ オブジェクトを使用して、送信側オブジェクトの側で圧縮に適した同等の処理を実行できます。

L2 2 番目の制限はQCoreApplication::compressEvent、イベント リストが非公開で定義されているため、完全に再実装できないことです。圧縮されているイベントに引き続きアクセスでき、それを削除するかどうかを決定することもできますが、イベント リストを反復処理する方法はありません。したがって:

  1. イベント キューはsendPostedEvents、内部から再帰的に呼び出すことによって暗黙的にアクセスできますnotify(したがって、eventFilter()event()またはスロットからも)。QCoreApplication::sendPostedEventsイベントが 経由で配信されている間は、イベント ループ ミューテックスを保持できない (そして保持しない) ため、これによってデッドロックが発生することはありませんsendEvent。イベントは次のようにフィルタリングできます。

    • 再実装された でグローバルにQCoreApplication::notify
    • 、を登録することにより、グローバルQInternal::EventNotifyCallback
    • オブジェクトにイベント フィルターをアタッチすることにより、ローカルで、
    • QObject::event()ターゲットクラスで再実装することにより、ローカルで明示的に。

    重複したイベントは引き続きイベント キューにポストされます。notify内部からの再帰呼び出しは、sendPostedEventsかなりのスタック スペースを消費します (64 ビット ポインター アーキテクチャでは 1kb の予算)。

  2. QCoreApplication::removePostedEvents既に存在するイベントは、新しいイベントをオブジェクトに投稿する前に呼び出すことで削除できます。残念ながら、QCoreApplication::compressEventイベント キューのミューテックスが既に保持されているため、この範囲内でこれを行うとデッドロックが発生します。

    レシーバー オブジェクトへのポインターを含むカスタム イベント クラスはremovePostedEvents、コンストラクターで自動的に呼び出すことができます。

  3. などの既存の圧縮されたイベントは、QEvent::Exit再流用できます。

    これらのイベントのセットは実装の詳細であり、変更される可能性があります。QObjectQt は、レシーバーポインター以外でこれらのイベントを区別しません。実装には、各タプル (イベント タイプ、レシーバー オブジェクト) ごとにプロキシ QObject のオーバーヘッドが必要です。

実装

以下のコードは、Qt 4 と Qt 5 の両方で機能します。後者では、必ずQT += core-privateqmake プロジェクト ファイルに追加して、プライベート Qt ヘッダーが含まれるようにしてください。

Qt 内部ヘッダーを使用しない実装は、他の回答で提供されます。

によって選択された 2 つのイベント削除コード パスがありますif (true)。有効なコード パスは最新のイベントを保持し、通常は最も理にかなっています。または、最も古いイベントを保持することもできます。これは、無効化されたコード パスが行うことです。

スクリーンショット

#include <QApplication>
#include <QMap>
#include <QSet>
#include <QMetaMethod>
#include <QMetaObject>
#include <private/qcoreapplication_p.h>
#include <private/qthread_p.h>
#include <private/qobject_p.h>

#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QFormLayout>

// Works on both Qt 4 and Qt 5.

//
// Common Code

/*! Keeps a list of singal indices for one or more meatobject classes.
 * The indices are signal indices as given by QMetaCallEvent.signalId.
 * On Qt 5, those do *not* match QMetaObject::methodIndex since they
 * exclude non-signal methods. */
class SignalList {
    Q_DISABLE_COPY(SignalList)
    typedef QMap<const QMetaObject *, QSet<int> > T;
    T m_data;
    /*! Returns a signal index that is can be compared to QMetaCallEvent.signalId. */
    static int signalIndex(const QMetaMethod & method) {
        Q_ASSERT(method.methodType() == QMetaMethod::Signal);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
        int index = -1;
        const QMetaObject * mobj = method.enclosingMetaObject();
        for (int i = 0; i <= method.methodIndex(); ++i) {
            if (mobj->method(i).methodType() != QMetaMethod::Signal) continue;
            ++ index;
        }
        return index;
#else
        return method.methodIndex();
#endif
    }
public:
    SignalList() {}
    void add(const QMetaMethod & method) {
        m_data[method.enclosingMetaObject()].insert(signalIndex(method));
    }
    void remove(const QMetaMethod & method) {
        T::iterator it = m_data.find(method.enclosingMetaObject());
        if (it != m_data.end()) {
            it->remove(signalIndex(method));
            if (it->empty()) m_data.erase(it);
        }
    }
    bool contains(const QMetaObject * metaObject, int signalId) {
        T::const_iterator it = m_data.find(metaObject);
        return it != m_data.end() && it.value().contains(signalId);
    }
};

//
// Implementation Using Event Compression With Access to Private Qt Headers

struct EventHelper : private QEvent {
    static void clearPostedFlag(QEvent * ev) {
        (&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
    }
};

template <class Base> class CompressorApplication : public Base {
    SignalList m_compressedSignals;
public:
    CompressorApplication(int & argc, char ** argv) : Base(argc, argv) {}
    void addCompressedSignal(const QMetaMethod & method) { m_compressedSignals.add(method); }
    void removeCompressedSignal(const QMetaMethod & method) { m_compressedSignals.remove(method); }
protected:
    bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) {
        if (event->type() != QEvent::MetaCall)
            return Base::compressEvent(event, receiver, postedEvents);

        QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(event);
        if (! m_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) return false;
        for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
            QPostEvent &cur = *it;
            if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type())
                continue;
            QMetaCallEvent *cur_mce = static_cast<QMetaCallEvent*>(cur.event);
            if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
                    cur_mce->id() != mce->id())
                continue;
            if (true) {
              /* Keep The Newest Call */              
              // We can't merely qSwap the existing posted event with the new one, since QEvent
              // keeps track of whether it has been posted. Deletion of a formerly posted event
              // takes the posted event list mutex and does a useless search of the posted event
              // list upon deletion. We thus clear the QEvent::posted flag before deletion.
              EventHelper::clearPostedFlag(cur.event);
              delete cur.event;
              cur.event = event;
            } else {
              /* Keep the Oldest Call */
              delete event;
            }
            return true;
        }
        return false;
    }
};

//
// Demo GUI

class Signaller : public QObject {
    Q_OBJECT
public:
    Q_SIGNAL void emptySignal();
    Q_SIGNAL void dataSignal(int);
};

class Widget : public QWidget {
    Q_OBJECT
    QPlainTextEdit * m_edit;
    QSpinBox * m_count;
    Signaller m_signaller;
    Q_SLOT void emptySlot() {
        m_edit->appendPlainText("emptySlot invoked");
    }
    Q_SLOT void dataSlot(int n) {
        m_edit->appendPlainText(QString("dataSlot(%1) invoked").arg(n));
    }
    Q_SLOT void sendSignals() {
        m_edit->appendPlainText(QString("\nEmitting %1 signals").arg(m_count->value()));
        for (int i = 0; i < m_count->value(); ++ i) {
            emit m_signaller.emptySignal();
            emit m_signaller.dataSignal(i + 1);
        }
    }
public:
    Widget(QWidget * parent = 0) : QWidget(parent),
        m_edit(new QPlainTextEdit), m_count(new QSpinBox)
    {
        QFormLayout * l = new QFormLayout(this);
        QPushButton * invoke = new QPushButton("Invoke");
        m_edit->setReadOnly(true);
        m_count->setRange(1, 1000);
        l->addRow("Number of slot invocations", m_count);
        l->addRow(invoke);
        l->addRow(m_edit);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
        connect(invoke, &QPushButton::clicked, this, &Widget::sendSignals);
        connect(&m_signaller, &Signaller::emptySignal, this, &Widget::emptySlot, Qt::QueuedConnection);
        connect(&m_signaller, &Signaller::dataSignal, this, &Widget::dataSlot, Qt::QueuedConnection);
#else
        connect(invoke, SIGNAL(clicked()), SLOT(sendSignals()));
        connect(&m_signaller, SIGNAL(emptySignal()), SLOT(emptySlot()), Qt::QueuedConnection);
        connect(&m_signaller, SIGNAL(dataSignal(int)), SLOT(dataSlot(int)), Qt::QueuedConnection);
#endif
    }
};

int main(int argc, char *argv[])
{
    CompressorApplication<QApplication> a(argc, argv);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
    a.addCompressedSignal(QMetaMethod::fromSignal(&Signaller::emptySignal));
    a.addCompressedSignal(QMetaMethod::fromSignal(&Signaller::dataSignal));
#else
    a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("emptySignal()")));
    a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("dataSignal(int)")));
#endif
    Widget w;
    w.show();
    return a.exec();
}

#include "main.moc"
于 2014-01-05T22:04:25.993 に答える
6

私は自分のコメントを答えにしようとしています。ドキュメントにこの情報が欠けている、または少なくとも私にとっては明らかではないことに同意します。

詳細情報を取得するには、次の 2 つのオプションがあります。

1) トライアル

qDebug() または printf()/fprintf() ステートメントを「遅い」スレッドのスロットに入れ、出力される内容を確認します。これを数回実行して、結論を導き出します。

2) 確認

この方法については、ソース コードを読む必要があります。これは、メタ オブジェクト コンパイラです。moc はソースファイルからこれを取得します。これはもう少し複雑な調査ですが、確実性につながる可能性があります。

私の知る限り、すべての信号の放出は対応するイベントを投稿します。次に、イベントはスレッド クラス内の別のスレッドのキューに入れられます。関連する 2 つのソース コード ファイルは次のとおりです。

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int 優先度)

クラス QPostEventList : public QVector

トレードオフには 2 つのアプローチがあります。

データ ミューテーター スロットからビジー スロット操作をキューに入れる

主な利点は、ビジー操作中に信号が失われないことです。ただし、これは必要以上の操作を処理する可能性があるため、本質的に遅くなる可能性があります。

処理されるイベントごとにデータが再設定されますが、実際にビジーな操作は一度だけ実行するためにキューに入れられます。複数のイベントがある場合、必ずしも最初のイベントである必要はありませんが、それが最も簡単な実装です。

Foo::Foo(QObject *parent) : QObject(parent)
{
    ...
    connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(dataUpdateSlot(const QByteArray&)));
    connect(this, SIGNAL(queueBusyOperationSignal()), SLOT(busyOperation()));
    ...
}

void Foo::dataUpdateSlot(const QByteArray &data)
{
    m_data = data;

    if (busyOperationQueued);
        emit queueBusyOperationSignal();
        m_busyOperationQueued = true;
    }
}

void MyClass::busyOperationSlot()
{

    // Do the busy work with m_data here

    m_busyOperationQueued = false;    
}

接続/切断

アイデアは、処理を開始するときに対応する信号からスロットを切断することです。これにより、新しいシグナルの発行がキャッチされないことが保証され、スレッドが次のイベントを処理できるようになったら、スロットがシグナルに再度接続されます。

これにより、接続と次の処理の間でもスレッドにアイドル時間が発生しますが、少なくともこれはそれを実装する簡単な方法です。ここで実際に提供されていないより多くのコンテキストに応じて、パフォーマンスの違いは実際には無視できるほどです。

主な欠点は、ビジー操作中に信号が失われることです。

Foo::Foo(QObject *parent) : QObject(parent)
{
    ...
    connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(busyOperationSlot(const QByteArray&)));
    ...
}

void MyClass::busyOperationSlot(const QByteArray &data)
{
    disconnect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), this, SLOT(dataUpdateSlot(const QByteArray&)));

    // Do the busy work with data here

    connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(dataUpdateSlot(const QByteArray&)));
}

今後の考え

問題自体を回避するのではなく、最後のイベントを処理するようにイベント システムに明示的に指示するための便利な API (たとえば、processEvents() に似たメソッドですが、最後に投稿されたイベントのみを処理する引数を持つもの) があるかどうかを考えていました。そのような API のように見えますが、非公開です。

おそらく、誰かが機能要求を提出して、そのようなものを公開するでしょう。

/*!
\internal
Returns \c true if \a event was compressed away (possibly deleted) and should not be added to the list.
*/
bool QCoreApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents)

関連するソース コードはここにあります。

また、 と にオーバーライドされたバージョンがあるようQGuiApplicationですQApplication

完全性については、次のような方法もあります。

void QCoreApplication::removePostedEvents(QObject * レシーバー、int eventType = 0) [静的]

レシーバーの postEvent() を使用して投稿された、指定された eventType のすべてのイベントを削除します。

イベントはディスパッチされず、代わりにキューから削除されます。この関数を呼び出す必要はありません。呼び出す場合は、イベントを強制終了すると、レシーバーが 1 つ以上の不変条件を破る可能性があることに注意してください。

receiver が null の場合、すべてのオブジェクトの eventType のイベントが削除されます。eventType が 0 の場合、レシーバーのすべてのイベントが削除されます。この関数を eventType を 0 にして呼び出すことは絶対に避けてください。この方法で呼び出す場合は、イベントを強制終了すると、レシーバーが 1 つ以上の不変条件を壊す可能性があることに注意してください。

しかし、これは、ドキュメンテーションに従って、ここにあるものとはまったく異なります。

于 2014-01-01T11:19:48.070 に答える
3

これは、Qt 4 と Qt 5 の両方に移植可能なさらに別のアプローチであり、Qt の内部 (パブリック ヘッダーを介して利用できるもの以外) へのアクセスを必要としません。Qt 5 では、Qt 4 スタイルの接続のみがサポートされています。圧縮されたエンティティは (受信側オブジェクト、スロット) のペアです。これは、 に完全にアクセスできる場合に使用される (送信者、受信者、シグナル、スロット) タプルとは異なりますQMetaCallEvent

を利用してQObject::qt_metacall、ブラック ボックスからコールの詳細をスヌープしますQMetaCallEvent私の他のno-internals answersendPostedEventsと同じように、への再帰が使用されます。

QObject::qt_metacallの API は、少なくとも Qt 4.0 以降変更されていないことに注意してください。

スクリーンショット

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QFormLayout>
#include <QSet>
#include <QMetaMethod>

// Common Code

/*! Keeps a list of method indices for one or more meatobject classes. */
class MethodList {
    Q_DISABLE_COPY(MethodList)
    typedef QMap<const QMetaObject *, QSet<int> > T;
    T m_data;
public:
    MethodList() {}
    template <class T> void add(const char * slot) {
        add(T::staticMetaObject.method(T::staticMetaObject.indexOfSlot(slot)));
    }
    void add(const QMetaMethod & method) {
        Q_ASSERT(method.methodIndex() >= 0);
        m_data[method.enclosingMetaObject()].insert(method.methodIndex());
    }
    void remove(const QMetaMethod & method) {
        T::iterator it = m_data.find(method.enclosingMetaObject());
        if (it != m_data.end()) {
            it->remove(method.methodIndex());
            if (it->empty()) m_data.erase(it);
        }
    }
    bool contains(const QMetaObject * metaObject, int methodId) {
        T::const_iterator it = m_data.find(metaObject);
        return it != m_data.end() && it.value().contains(methodId);
    }
};
Q_GLOBAL_STATIC(MethodList, compressedSlots)

// Compressor

class Compressor : public QObject {
    enum { Idle, Armed, Valid } m_state;
    QMetaObject::Call m_call;
    int m_methodIndex;
    QSet<int> m_armed; // armed method IDs

    int qt_metacall(QMetaObject::Call call, int id, void ** args) {
        if (m_state != Armed) return QObject::qt_metacall(call, id, args);
        m_state = Valid;
        m_call = call;
        m_methodIndex = id;
        return 0;
    }
    bool eventFilter(QObject * target, QEvent * ev) {
        Q_ASSERT(target == parent());
        if (ev->type() == QEvent::MetaCall) {
            m_state = Armed;
            if (QT_VERSION < QT_VERSION_CHECK(5,0,0) || ! *(void**)(ev+1)) {
                // On Qt5, we ensure null QMetaCallEvent::slotObj_ since we can't handle Qt5-style member pointer calls
                Compressor::event(ev); // Use QObject::event() and qt_metacall to extract metacall data
            }
            if (m_state == Armed) m_state = Idle;
            // Only intercept compressed slot calls
            if (m_state != Valid || m_call != QMetaObject::InvokeMetaMethod ||
                    ! compressedSlots()->contains(target->metaObject(), m_methodIndex)) return false;
            int methodIndex = m_methodIndex;
            m_armed.insert(methodIndex);
            QCoreApplication::sendPostedEvents(target, QEvent::MetaCall); // recurse
            if (! m_armed.contains(methodIndex)) return true; // Compress the call
            m_armed.remove(methodIndex);
        }
        return false;
    }
public:
    Compressor(QObject * parent) : QObject(parent), m_state(Idle) {
        parent->installEventFilter(this);
    }
};

//
// Demo GUI

class Signaller : public QObject {
    Q_OBJECT
public:
    Q_SIGNAL void emptySignal();
    Q_SIGNAL void dataSignal(int);
};

class Widget : public QWidget {
    Q_OBJECT
    QPlainTextEdit * m_edit;
    QSpinBox * m_count;
    Signaller m_signaller;
    Q_SLOT void emptySlot() {
        m_edit->appendPlainText("emptySlot invoked");
    }
    Q_SLOT void dataSlot(int n) {
        m_edit->appendPlainText(QString("dataSlot(%1) invoked").arg(n));
    }
    Q_SLOT void sendSignals() {
        m_edit->appendPlainText(QString("\nEmitting %1 signals").arg(m_count->value()));
        for (int i = 0; i < m_count->value(); ++ i) {
            emit m_signaller.emptySignal();
            emit m_signaller.dataSignal(i + 1);
        }
    }
public:
    Widget(QWidget * parent = 0) : QWidget(parent),
        m_edit(new QPlainTextEdit), m_count(new QSpinBox)
    {
        QFormLayout * l = new QFormLayout(this);
        QPushButton * invoke = new QPushButton("Invoke");
        m_edit->setReadOnly(true);
        m_count->setRange(1, 1000);
        l->addRow("Number of slot invocations", m_count);
        l->addRow(invoke);
        l->addRow(m_edit);
        connect(invoke, SIGNAL(clicked()), SLOT(sendSignals()));
        m_edit->appendPlainText(QString("Qt %1").arg(qVersion()));
        connect(&m_signaller, SIGNAL(emptySignal()), SLOT(emptySlot()), Qt::QueuedConnection);
        connect(&m_signaller, SIGNAL(dataSignal(int)), SLOT(dataSlot(int)), Qt::QueuedConnection);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    compressedSlots()->add<Widget>("emptySlot()");
    compressedSlots()->add<Widget>("dataSlot(int)");
    Widget w;
    new Compressor(&w);
    w.show();
    return a.exec();
}

#include "main.moc"
于 2014-01-10T01:26:30.877 に答える
2

質問から: 「すべてのシグナルを処理する場合、thread_slow に最後のシグナルのみを処理させる方法はありますか?」

常に最後のシグナルを処理したいだけで、処理が遅くならない限り余分なシグナルがほとんどQThread::exec()処理されなくても構わない場合は、通常のイベント ループを使用して、このような非常に単純なアプローチを試すことができます。これらのスロット メソッドをQObjectサブクラスに入れ、それをスレッドに移動します。

//slot
void MyClass::publicReceiverSlotForQueuedSignals(QString data)
{
    // Update data every time
    mReceivedData = data;

    // Allow worker method to be queued just once
    if (!mWorkerSlotInvoked) {
        mWorkerSlotInvoked = true;
        QMetaObject::invokeMethod(this, "workerSlot", Qt::QueuedConnection);
        qDebug() << "publicReceiverSlotForQueuedSignals: invoked workerSlot!"
                 << "New data:" << mReceivedData;
    } else {
        qDebug() << "publicReceiverSlotForQueuedSignals: workerSlot already invoked."
                 << "New data:" << mReceivedData;
    }
}

//slot
void MyClass::privateWorkerSlot()
{
    mWorkerSlotInvoked = false;
    qDebug() << "workerSlot for data:" << mReceivedData;
    QThread::msleep(3000);
    qDebug() << "workerSlot returning.";
}

publicReceiverSlotForQueuedSignals非常に高速に処理されるため (高速呼び出しqDebugelseはおそらく最も時間がかかる部分です)、キューに入れられたシグナルの数は実際には問題になりません。そして、privateWorkerSlotそのスレッドのイベント ループ ローテーションごとに 1 回だけ呼び出されます。

また、両方のスロット メソッド (およびそれらを使用する他のすべての場所) でmReceivedData保護するためにミューテックスを追加することも簡単です。mWorkerSlotInvoked次に、スロットに直接接続できます。これinvokeMethodは、スレッド セーフであり、mutex により、スレッド セーフのプライベート データ メンバーの処理も行われるためMyClassです。mReceivedData時間のかかる処理を行う前に、ローカル変数に内容をコピーしてミューテックスのロックを解除してください。

注: テストされていないコードで、おそらくいくつかの間違いがあります。

于 2014-01-01T12:51:23.140 に答える
1

DirectConnectionQueueConnectionの組み合わせを使用できます。

  1. ワーカー側 ( thread_slow):

    • タスク プロバイダーによって呼び出されることを意図したパブリック スロット ( thread_fast)

      void Working::process()
      {
         if (working)
         {
           printf("Drop a task %p\n", QThread::currentThread()); 
           return;
         }
      
        working = true;        
        emit realWork();
      }
      
    • 処理関数 (遅い) :realProcess()

      void Working::realProcess()
      {
          printf("      **** Working ... %p\n",QThread::currentThread()); fflush(stdout);
      
          // Emulate a big processing ...
          usleep(3000*1000);
      
          printf("      **** Done. %p\n",QThread::currentThread());fflush(stdout);
          working = false;
          emit done();
      }
      
    • からへのQueueConnectionrealWorkrealProcess

      Working::Working()
      {
          working = false;
          connect(this,SIGNAL(realWork()),this,SLOT(realProcess()),Qt::QueuedConnection);
      }
      
  2. タスク プロバイダー側​​ ( thread_fast)

    • startWork()信号_

      void TaskProv::orderWork()
      {
          emit startWork();
      }
      
    • ワーカー プロセス スロットへのDirectConnection

      QObject::connect(&taskProvider,SIGNAL(startWork()),&worker,SLOT(process()),Qt::DirectConnection);
      

いくつかのメモ:

  • 関数Working::process()thread_fast(ワーカー メンバー関数であっても) で実行されますが、フラグをチェックするだけなので、処理時間には影響しません。

  • 余分なタスクがドロップされる可能性が気になる場合は、ワーカーの作業フラグをミューテックスで保護して、より厳密に管理できます。

  • これは、接続タイプが Direct と Queue の正しい組み合わせである必要があることを除いて、lpapp の「データ ミューテーター スロットからのビジー スロット操作をキューに入れる」に非常に似ています。

于 2015-03-24T21:40:34.777 に答える