Soru Qt sıralı bağlantı kullanırken yuva aramaları nasıl sıkıştırılır?


Bazı makaleleri okuduktan sonra bunun gibi Qt Sinyal Yuvası haberleşmesi hakkında Kuyrukta bağlantı ile ilgili hala bir sorum var.

Birbirimize sürekli sinyal gönderen bazı mesajlar varsa ve bir tane söyleyelim thread_slowolay döngüsü ve başka bir yavaş bir yöntem çalışıyor thread_fast diğer iş parçacığı hala çalışırken yavaş bir yöntem çalışırken yavaş bir yöntem ..... çalışırken birden çok sinyal gönderen hızlı bir koşuyor thread_slow olay döngüsüne geri döner, daha önce gönderilen tüm sinyalleri işleyecek thread_fastya da sadece sonuncusu (tüm sinyaller aynı tipte)?

Eğer tüm sinyalleri işleyecekse, bunu yapmanın bir yolu var mı? thread_slow  sadece sonuncusu işlemek? (Bir çoklu-başlık uygulamasında "sonuncusu" göz önüne alındığında belirsiz olabilir, iş parçacığı son sinyal için son sinyali dikkate alalım, basitlik uğruna, iş parçacığı için sonuncusu görünene kadar yenileri gönderilebilir. ).

(Bunu soruyorum çünkü birden çok iş parçacığı veri alma birden çok iş parçacığım var, ve bunların eski verileri, yalnızca gönderilen son verileri işlemesini istemiyorum)

Bazı testler yaptım ve Qt'nin tüm sinyalleri işleyeceği anlaşılıyor. Bir iş parçacığı yaptım:

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

ve başka bir yuva yapacak:

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

ve iş parçacığı çalışır:

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

Bir iş parçacığından 500 milisaniyeden diğerine bir sinyal gönderiyorum ve diğer iş parçacığı 3000 milisaniye (3 saniye) uyuyor ve ardından bir iç değişkeni 100'e kadar artırıyor ve artırıyor. Yuvası her yürütüldüğünde alınan değer + iç değişken. Benim elde ettiğim sonuç her seferinde QCoreApplication::processEvents(); çağrılır, tüm sinyaller yürütülür .... (Bu bölümü düzenledim çünkü önceki kodumda bir hata buldum)


21
2018-01-01 08:52


Menşei


Test etmek gerçekten çok basit. İlgili yuvaya bir qDebug () koyun ve kaç kez yazdırıldığını görün. Birkaç kez yapın ve sonucu alın ya da denemeye güvenmiyorsanız ilgili kodu kendiniz kontrol edin. Bahsetmemek gerekirse, her zaman "yavaş" iş parçacığında paylaşılan bir değişken yazabilir ve "hızlı" verileri oradan alır. - lpapp
Cevabımı kontrol et, eventloop ile ilgili kanıt getirdim - Kikohs
@Kikohs: bağlantınız kaynak kodun yanlış yerine işaret ediyor gibi görünüyor. İki özdeş cevaplara ihtiyacımız olduğundan emin değilim, ancak siz bu prensipleri diğerlerine benzer şekilde anlamlı bir şekilde yaptığınızı düşünün. En azından, acıtmıyor, sanırım. :) - lpapp
Bu soruna bir + 1 verdim (içeriğim cevabımda verilen ipuçlarına bakılmaksızın düzenlense bile) çünkü bu özelliğe sahip olmak için kamuya açık API düşüncesinin gıda olabileceğini düşünüyorum. Belki de, Qt içinde reddedilirdi, ama en azından tartışmak için bir konuya sahip olmak güzel. - lpapp
WOW LaszloPapp'dan +1 aldım mı ?! Birisi iyi bir yeni yıl partisi vardı! hahaha - mFeinstein


Cevaplar:


Yorumumu bir cevap haline getirmeye çalışıyorum. Belgelerin bu bilgiden yoksun olduğu konusunda ya da en azından benim için açık değil ve görünüşe göre sizin için de açıkça kabul ediyorum.

Daha fazla bilgi almak için iki seçenek olacaktır:

1) Deneme

QDebug () veya printf () / fprintf () ifadesini "yavaş" iş parçacığındaki yuvanıza yerleştirin ve ne yazdırdığını görün. Bunu birkaç kez çalıştırın ve sonucu çizin.

2) emin olmak

Bunun için meta kodu derleyici, aka. moc bunu kaynak dosyadan alır. Bu biraz daha ilgili bir soruşturma, ancak bu kesinliğe yol açabilir.

Bildiğim kadarıyla, her sinyal emisyonu uygun bir olay yayınlıyor. Ardından, iş parçacığı sınıfındaki ayrı iş parçacığı için sıraya alınacaktır. Burada ilgili iki kaynak kodu dosyasını bulabilirsiniz:

void QCoreApplication :: postEvent (QObject * alıcısı, QEvent * olayı, int priority)

ve

QPostEventList sınıfı: public QVector

Onların trade-off ile iki yaklaşım vardır:

Veri mutator yuvasından meşgul bir slot işlemi sırası

Ana avantaj, meşgul işlem sırasında sinyallerin kaybolamayacağıdır. Ancak, ihtiyaç duyulandan çok daha fazla işlem gerçekleştirebileceğinden, bu doğal olarak daha yavaş olabilir.

Buradaki fikir, ele alınan her olay için verilerin yeniden ayarlanmasıdır, ancak gerçek meşgul işlemi yalnızca bir kez yürütme için sıraya konulur. Daha fazla varsa, ilk etkinlik için zorunlu olmak zorunda değildir, ancak bu en basit uygulamadır.

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

Bağlan / Bağlantıyı kes

Buradaki fikir, işleme başladığında yuvayı karşılık gelen sinyalden ayırmaktır. Bu, yeni sinyal yayılımının yakalanmayacağından emin olacak ve iplik sonraki olayları işlemek için serbest kaldığında yuvayı tekrar sinyale bağlayacaktır.

Bu, bağlantıda ve hatta bir sonraki iş arasında olsa da, işin içinde boşta vakit geçirebilirdi, ama en azından bu, bunu basit bir şekilde imha etmenin yolu olacaktı. Aslında burada sağlanmayan daha fazla bağlama bağlı olarak bir performans farkı bile ihmal edilebilir.

Ana dezavantajı, bu yoğun operasyon sırasında sinyalleri kaybedecek olmasıdır.

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

Gelecek düşünceler

Uygun bir API olup olmadığını düşünüyordum - ör. processEvents () yönteminin her ikisi de, yalnızca yayınlanmış olan son olayı işlemek için bir argümana sahiptir - gerçekte olay sistemini, sorunun kendisini atlatmak yerine, sonuncusu işlemek için açıkça belirtir. Böyle bir API gibi görünmektedir, ancak, özeldir.

Belki, birileri kamusal alanda böyle bir şey yapmak için bir özellik isteği gönderir.

/*!
\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)

İlgili kaynak kodu bulunabilir İşte.

Aynı zamanda içinde geçersiz bir sürümü var gibi görünüyor QGuiApplication ve QApplication.

Bütünlük için olduğu gibi, böyle bir yöntem de vardır:

void QCoreApplication :: removePostedEvents (QObject * alıcı, int eventType = 0) [statik]

Receiver için postEvent () kullanılarak gönderilen eventType olaylarının tüm olaylarını kaldırır.

Olaylar gönderilmez, bunun yerine kuyruktan kaldırılır. Bu işlevi çağırmaya hiç gerek yok. Eğer onu ararsanız, öldürme olaylarının alıcının bir veya daha fazla değişmezi kırmasına neden olabileceğini unutmayın.

Alıcı boşsa, eventType olayları tüm nesneler için kaldırılır. EventType 0 ise, tüm olaylar alıcı için kaldırılır. Bu işlevi hiçbir zaman 0lık eventType ile çağırmamalısınız. Eğer bu şekilde çağırırsanız, öldürme olaylarının alıcının bir veya daha fazla değişkeni kırmasına neden olabileceğini unutmayın.

Ancak bu, belgelere göre burada olmak istediğiniz şey değil.


4
2018-01-01 11:19



Ama çalışma zamanında dinamik olarak kesmek ve yeniden bağlamak mümkün mü? Tüm kongrelerde bir liste var mı? - mFeinstein
@mFeinstein: stackoverflow.com/questions/2755694/... - lpapp
@mFeinstein: Ama aynı zamanda cevabımı gör. Bağlantıların bir listesini almanın kolay bir yolu yok. - Kuba Ober
@KubaOber Az önce gördüm, evet öyle görünüyor ki işler o kadar kolay değil: / Ben Qt'deki insanlar bu ipliği görmeyi umuyorum .... - mFeinstein
@mFeinstein: Birkaç kişi gördü. Ben de özellikle son zamanlarda bir Qt geliştiricisiyim, özellikle de son zamanlarda aptalca bir katkı yapmamama rağmen. Ancak, bunu birkaç kişi ile tartıştım. Her neyse, görünüşe göre birisi hakkında çok endişeli olan çok yaygın bir durum gibi değil. - lpapp


QCoreApplication QMetaCallEvent Sıkıştırma

Her bir sıralı çağrı, bir QMetaCallEvent hedef nesneye Olay gönderen nesnesini, sinyal kimliğini, yuva endeksini ve paketlenmiş çağrı parametrelerini içerir. Qt 5’te, sinyal kimliği genellikle tarafından döndürülen değere eşit değildir QMetaObject::signalIndex(): Nesnenin sadece sinyal yöntemleri ve başka yöntemleri olmadığı gibi hesaplanan bir dizin.

Amaç, bu gibi çağrıları sıkıştırmaktır, böylece belirli bir sıralama (gönderici nesnesi, gönderici sinyali, alıcı nesnesi, alıcı yuvası) için olay kuyruğunda sadece bir tek çağrı bulunmaktadır.

Bu, kaynak veya hedef nesneleri değiştirmek zorunda kalmadan ve asgari yükü korurken, bunu yapmanın tek akıl yoludur. Diğer cevaplarımdaki event-loop-recursing yöntemleri, Qt 64-bit-pointer mimarileri için oluşturulduğunda 1kbyte sırasına göre, her olay başına ciddi yığın yüküne sahiptir.

Yeni olaylar bir nesneye gönderildiğinde olay kuyruğuna erişilebilir Bu, bir veya daha fazla etkinlik zaten ona gönderilmiş. Böyle bir durumda, QCoreApplication::postEvent aramalar QCoreApplication::compressEvent. compressEvent ilk olay bir nesneye gönderildiğinde çağrılmaz. Bu yöntemin yeniden tasarlanmasında, bir QMetaCallEvent Hedef nesneye gönderilenler, aramanıza bir çağrı için kontrol edilebilir ve eski kopyaların silinmesi gerekir. Tanımları elde etmek için özel Qt başlıkları dahil edilmelidir QMetaCallEvent, QPostEvent ve QPostEventList.

Artıları: Ne gönderen ne de alıcı nesnelerin bir şeyden haberdar olmaları gerekmiyor. Qt 5'te yöntem-işaretçi çağrıları dahil olmak üzere, sinyaller ve yuvalar olduğu gibi çalışır. Qt kendisi, bu şekilde sıkıştırma olaylarını kullanır.

Eksileri: Özel Qt başlıklarının dahil edilmesini ve zorlu takas edilmesini gerektirir QEvent::posted bayrağı.

Hack yerine QEvent::posted bayrak, silinecek olaylar ayrı bir listede sıraya konabilir ve compressEvent Sıfır süreli zamanlayıcı tetiklendiğinde, arama yapın. Bu, ek bir etkinlik listesi ve her biri için Gönderilen etkinlik listesinde yinelenen etkinlik silme.

Diğer Yaklaşımlar

Bunu başka bir şekilde yapma noktası, Qt'ın dahili özelliklerini kullanmamaktır.

L1 İlk sınırlama, özel olarak tanımlanmış içeriğe erişim içermiyor QMetaCallEvent. Aşağıdaki gibi ele alınabilir:

  1. Kaynak ve hedef nesneler arasında hedef ile aynı imzalara sahip sinyaller ve yuvaları olan bir proxy nesnesi bağlanabilir.

  2. Koşuyor QMetaCallEvent Bir proxy nesnesinde, çağrı tipinin, çağrılan slot kimliğinin ve argümanların çıkarılmasına izin verilir.

  3. Sinyal yuvası bağlantılarının yerine, olaylar açıkça hedef nesneye gönderilebilir. Hedef nesne veya bir olay filtresi, olay çağrısından olay çağrısını açıkça yeniden sentezlemelidir.

  4. Bir gelenek compressedConnect yerine kullanılabilir QObject::connect. Bu, sinyal ve yuva ayrıntılarını tamamen ortaya çıkarır. Bir sıkıştırma nesnesinin sıkıştırma dostu eşdeğerini gerçekleştirmek için bir proxy nesnesi kullanılabilir. queued_activate Gönderenin nesnesinin yanında.

L2 İkinci sınırlama tamamen yeniden hayata geçirilemiyor QCoreApplication::compressEventOlay listesi özel olarak tanımlandığından. Hala sıkıştırılmış olaya erişebiliyoruz ve yine de silip silmeyeceğine karar verebiliriz, ancak olay listesini yinelemenin yolu yok. Böylece:

  1. Olay sırasına, örtülü olarak çağırarak çağrılabilir sendPostedEvents içinden notify (ayrıca eventFilter(), event() veya yuvalardan). Bu bir çıkmazın sebebi değil QCoreApplication::sendPostedEvents etkinlik teslim edilirken bir etkinlik döngüsü mutex'i tutamaz (ve yapmaz) sendEvent. Etkinlikler aşağıdaki gibi filtrelenebilir:

    • reimplemented küresel olarak QCoreApplication::notify,
    • dünya çapında bir QInternal::EventNotifyCallback,
    • nesnelere bir etkinlik filtresi ekleyerek yerel olarak,
    • reimplementing tarafından açıkça yerel olarak QObject::event() hedef sınıfta.

    Yinelenen olaylar hala olay kuyruğunda yayınlanır. Özyinelemeye notify içinden sendPostedEvents oldukça fazla yığın alanı (64 bit işaretçi mimarileri üzerinde bütçe 1kb) tüketin.

  2. Mevcut olaylar zaten arayarak kaldırılabilir QCoreApplication::removePostedEvents bir nesneye yeni bir olay göndermeden önce. Ne yazık ki bunu içinde yapıyor QCoreApplication::compressEvent event queue mutex zaten tutulduğu için bir kilitlenme neden olur.

    Alıcı nesnesine işaretçi içeren özel bir etkinlik sınıfı otomatik olarak arayabilir removePostedEvents yapıcıda.

  3. Gibi mevcut sıkıştırılmış olaylar QEvent::Exit, yeniden yetkilendirilebilir.

    Bu olayların seti bir uygulama detayıdır ve değişebilir. Qt, alıcıdan başka olaylar arasında ayrım yapmaz QObject Işaretçi. Bir uygulama, her bir proxy QObject'in (olay tipi, alıcı nesne) tuple başına yükü gerektirir.

uygulama

Aşağıdaki kod hem Qt 4 hem de Qt 5'te çalışır. Sonunda, eklediğinizden emin olun. QT += core-private Qmake proje dosyasına, böylece özel Qt başlıkları dahil olsun.

Qt iç başlıklarını kullanmayan uygulamalar diğer cevaplarda verilmiştir:

Tarafından seçilen iki etkinlik kaldırma kodu yolu vardır. if (true). Etkinleştirilmiş kod yolu, en son olayı korur ve genellikle en mantıklıdır. Alternatif olarak, en eski etkinliği korumak isteyebilirsiniz - devre dışı bırakılan kod yolu budur.

screenshot

#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"

13
2018-01-08 20:49



Gördüğüm kadarıyla bu sadece gerçek bir çözüm olabilir (yani sessizce çalışan ve hatta onu bile bilmeyen nesneler), sadece son sinyali almak için, ama QT'nin iç işlerinde yapılması gereken bir utanç… Umarım bu işlevselliği gelecekte sağlayabilirler ... - mFeinstein
> Her sıraya yarık aramak.<Olabilir işaret? - kyb
@kyb Bu genel bir terim, gerçekten. Sinyal çağrılarını da sıraya alabilirsiniz, ancak kuyruktaki son nokta, gönderen taraf değil, alıcı uçtur. Böylece, sıralı yuva çağrısı daha uygundur: sıraya alınmış çağrının, gönderenin dışında, gönderenle bağlantısı yoktur. QObject::sender() mekanizması. - Kuba Ober
@KubaOber, daha fazla makale ve yazı okuduktan sonra söylemek istiyorum sinyaller oluşturmak ve göndermek olaylar sıraya, sıradan alınan ve karşılık gelen yuvaya göre işlenen olaylar. Haklısın. - kyb


Bu başka bir yaklaşım. Gönderen ve alıcı nesnelerinde değişiklik yapılmasını gerektirmez, ancak bir özel gerektirir CompressorProxy nesne. Bu, hem Qt 4 hem de Qt 5 için taşınabilirdir ve Qt'nin dahili bilgisayarlarına erişim gerektirmez.

Kompresör nesnesi şart Hedef nesnenin çocuğu olun - yuvaları olan. Bu şekilde hedef nesnenin iş parçacığını izler. Kompresörün sinyalleri hedefin yuvalarına bağlandığından, aynı iplik içerisindeyken, hedef yuva çağrıları için sıralı bağlantıların hiçbir başı yoktur.

Büyü olur emitCheck yöntem: kendini yinelemeli olarak çağırır.

  1. Bir yuva çağrısı biter emitCheck.
  2. Daha fazla yayınlanmış etkinlik arayarak gönderilir sendPostedEvents.
  3. Etkinlik kuyruğunda yinelenen yuva çağrıları varsa, bunlar sonuçta sona erer emitCheck tekrar.
  4. Kuyruktaki son olay alındıktan sonra ve sendPostedEvents artık tekrarlanmaz, verilen yuva için bir bayrak sıfırlanır, böylece proxy sinyali bir kereden fazla yayınlanmaz. İstenen sıkıştırma davranışı bu.

Herhangi bir sıraya alınmış sıra kümesi için bir örnek çağrılır. CompressorProxy, emitCheck dönecek true Yayınlanan etkinlik listesinden bir geçişte birden çok kez çağrılan bir yuva için yalnızca bir kez.

Serbest bırakma modunda özyinelemeli çağrı başına yığın kullanımının 32 bit mimarilerde 600 bayt civarında ve 64 bit mimarilerde iki katı olduğunu unutmayın. OS X'de hata ayıklama modunda, 64 bitlik bir yapı kullanarak, yineleme başına yığın kullanımı ~ 4kb'dir.

screenshot

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

class CompressorProxy : public QObject {
    Q_OBJECT
    bool emitCheck(bool & flag) {
        flag = true;
        QCoreApplication::sendPostedEvents(this, QEvent::MetaCall); // recurse
        bool result = flag;
        flag = false;
        return result;
    }

    bool m_slot;
    Q_SLOT void slot() {
        if (emitCheck(m_slot)) emit signal();
    }
    Q_SIGNAL void signal();

    bool m_slot_int;
    Q_SLOT void slot_int(int arg1) {
        if (emitCheck(m_slot_int)) emit signal_int(arg1);
    }
    Q_SIGNAL void signal_int(int);
public:
    // No default constructor, since the proxy must be a child of the
    // target object.
    explicit CompressorProxy(QObject * parent) : QObject(parent) {}
};

//
// 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()));
        CompressorProxy * proxy = new CompressorProxy(this);
        connect(&m_signaller, SIGNAL(emptySignal()), proxy, SLOT(slot()), Qt::QueuedConnection);
        connect(&m_signaller, SIGNAL(dataSignal(int)), proxy, SLOT(slot_int(int)), Qt::QueuedConnection);
        connect(proxy, SIGNAL(signal()), this, SLOT(emptySlot()));
        connect(proxy, SIGNAL(signal_int(int)), this, SLOT(dataSlot(int)));
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

#include "main.moc"

4
2018-01-10 01:26





Bu, hem Qt4 hem de Qt5 için taşınabilir olan ve Qt'un dahili kullanıcılarına erişim gerektirmeyen bir başka yaklaşımdır (genel başlıklar aracılığıyla kullanılabilenler hariç). Qt 5'te, yalnızca Qt 4 tarzı bağlantılar desteklenir. Sıkıştırılmış varlıklar (alıcı nesne, yuva) çiftleridir. Bu, tam erişime sahip olduğunda kullanılan (gönderici, alıcı, sinyal, yuva) tuple'den farklıdır. QMetaCallEvent.

Kullanır QObject::qt_metacall kara kutusundan aramanın ayrıntılarını snoop yapmak için QMetaCallEvent. İçine dönüş sendPostedEvents tıpkı benim gibi dahili cevap yok.

Farketmeye değer QObject::qt_metacallAPI'sı en az Qt 4.0'dan beri değişmeden kalmıştır.

screenshot

#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"

3
2018-01-01 10:40



Haha, meatobject :) - Tim Angus
@TimAngus Bu yorum şu an donmuş durumda. Gerekirse kodu aşacaktır. - Kuba Ober


thread_slow 

sıra bağlantısı veya postEvent kullandıysanız, olay döngüsünde gönderilen tüm sinyalleri işleyecek

Kaynak:

Sıralı Bağlantı Kontrol, alıcının iş parçacığının olay döngüsüne döndüğünde, yuva çağrılır. Yuva, alıcının dişinde yürütülür.

QtDoc

Etkinliğin nasıl işlendiği hakkında daha fazla bilgi isterseniz, buraya bakabilirsiniz:

https://qt.gitorious.org/qt/qtbase/source/631c3dbc800bb9b2e3b227c0a09523f0f7eef0b7:src/corelib/thread/qthread_p.h#L127

Gördüğünüz gibi, olay öncelik sırasına göre sıralanır, böylece tüm etkinlikleriniz aynı önceliğe sahipse, ilk önce ilk çıkar.

Bu önemli bir görev değil, burada kaba bir girişim, işe yarayıp yaramadığını söyle.

Önerim, olayları temel olarak kendiniz saklamak ve sadece sonuncusu işlemektir.

thread_slow.h

int current_val;
bool m_isRunning;

thread_slow.cpp

void enqueue_slot( int val /*or whatever you value is*/ ) {
     // You'll enventually need a a QMutex here if your slot is not call in the thread
     m_current_val = val;
     if( !m_isRunning )
         slowRun();
}

void checkHasReceivedEventSlot() {
    if( m_current_val != -1 ) // Invalid value or a test condition
        slowRun();
}

void slowRun() {
    m_isRunning = true;
    int v = m_current_val;
    m_current_val = -1; // Invalid value

   // Do stuff with v

   // Let the queue fill itself with enqueue_slot calls
   QTimer::singleShot(kTIMEOUT, this, SLOT(checkHasReceivedEventSlot()));
}

Enqueue_slot ilk kez çağrıldı, yavaş çalışma başlayacak

DÜZENLE:

Son etkinlik olduğundan emin olmak için şunları yapabilirsiniz:

void checkHasReceivedEventSlot() {
    // Runs enqueue_slot until no more events are in the loop
    while( m_thread->eventDispatcher()->hasPendingEvents() )
         m_thread->eventDispatcher()->processEvents(QEventLoop::AllEvents);

    // m_current_val should hold the last event
    if( m_current_val != -1 ) // Invalid value or a test condition
        slowRun();
}

2
2018-01-01 12:51



"olay döngüsünde gönderilen tüm sinyalleri işleyecek." -> Cevabınız için buna bir kanıt sunabilir misiniz, çünkü eğer bu doğru değilse geri kalanı ilgisizdir, bu yüzden önce bir ön taraf olduğunu ispatlamanız gerekir. :) - lpapp
Ayrıca, .isEmpty () bir Qt programında .empty () yerine kullanmalısınız. Kodunuz da önemli bir içerikten yoksundur, 'v' gibi hiç kullanılmaz. CheckHasRecivedEventSlot () öğesinin ilk kez nasıl çağrılacağı açık değildir veya bu konu için slowRun () - lpapp
Dostum, reddetmeyi sevdiğini biliyorum, ilk defa değil. Bir girişim olduğunu söyledim. Kodu düzelttim. - Kikohs
İlk soru için, Qt tarafından gerekli, kendiniz test edin. Birçok olaya sırayla girerseniz, olay döngü yığmasını gerçekten üfleyebilirsiniz. - Kikohs
"Kendin test et" bir kanıt değil. Bir belge veya kaynak kodu bir kanıt olabilir. Ya ben geçici olarak iddia edersem farklı bir tecrübeye sahip olurum, kim doğru olur… Bu noktada, bu noktayı desteklemeden bir tahmindir. Aslında, bunu yedeklemek için basit bir örnek bile eklemezsiniz. - lpapp


Sorudan: "Eğer tüm sinyalleri işleyecekse, thread_slow'un sadece sonuncuyu işlemesi için bir yol var mı?"

Her zaman işlenen son sinyali almak istiyorsanız ve az ekstra sinyaller, herhangi bir şeyi yavaşlatmadığı sürece işlenir, o zaman düzenli bir şekilde bu şekilde çok basit bir yaklaşımı deneyebilirsiniz. QThread::exec() olay döngüsü. Bu slot yöntemlerini bir QObject Daha sonra bir iş parçacığına taşıdığınız alt sınıf:

//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 çok hızlı geçerqDebug içinde else Muhtemelen hızlı aramalar için en çok zaman harcayan kısımdır), bu yüzden kaç tane sinyalin kuyruğa alındığı önemli değildir. Ve sonra privateWorkerSlot ne kadar yavaş olursa olsun, o iş parçacığı için bir döngü döngü dönüşü başına yalnızca bir tane çağrılır.

Ayrıca korumak için bir mutex eklemek önemsiz olacaktır mReceivedData ve mWorkerSlotInvoked Her iki yuva yönteminde (ve başka her yerde bunları kullanabilirsiniz). Ardından, yuvaya doğrudan bağlantı kurabilirsiniz, çünkü invokeMethod iş parçacığı güvenlidir ve mutex özel verileri MyClass iş parçacığı da güvenli. Sadece içeriğini kopyaladığınızdan emin olun mReceivedData Yerel bir değişkene ve muteks kilidini açmadan önce, zaman alıcı bir işlem yapmadan önce.

Not: test edilmemiş kod, muhtemelen birkaç hata vardır.


2
2018-03-24 21:40



SON sinyalini İLK DEĞİL istiyorum, bu şekilde en güncel veriye sahibim. - mFeinstein
@mFeinstein Bu LAST sinyalini verecek ... Ya da en azından neredeyse son, diğer iş parçacığı sonra sinyalleri sıraya olabilir bir pencere var privateWorkerSlot çağrılır, ancak aslında çağrılmadan önce. Ancak, kamusal alan çok hızlı işlendiğinden ve başka zaman harcayan işlemlerin olmadığı varsayıldığında, pencere çok kısadır. Ve muteksin eklenmesi bile bunu çözecektir. - hyde
@hyde: Kodunuza göre bunun nasıl olacağını anlayamıyorum. Temel olarak, bekçi ve zamanlayıcı tarafından garanti edilen ilk çağrıdan sonra durmayı durdurursunuz. Ayrıca, sağlam bir işlem için "hızlı hızlı" yeterli değildir. :) Ayrıca, iş parçacığı senkronizasyonu ilkellerini ekleseniz bile, kodunuz yalnızca son gönderilen etkinliğin işlenmesini sağlamayacaktır çünkü bunun nedeni, etkinlik dağıtıcısı IMO'suyla başa çıkmanızdır. Bunun için tek güvenilir kaynak budur. - lpapp
@hyde Ben ilk önce size sormak istedim beri downvote, çünkü belki de ben kod anlamadı ... downvoting LaszloPapp hobi benim değil: P (Kikohs yorum bakın) - mFeinstein
@mFeinstein Hayır sorun onları :) - hyde


Kombinasyonunu kullanabilirsiniz directconnection ve QueueConnection:

  1. İşçi tarafındathread_slow):

    • Görev sağlayıcınız tarafından çağrılması amaçlanan bir ortak yuva (thread_fast)

      void Working::process()
      {
         if (working)
         {
           printf("Drop a task %p\n", QThread::currentThread()); 
           return;
         }
      
        working = true;        
        emit realWork();
      }
      
    • Bir işlem işlevi (yavaş): 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();
      }
      
    • bir QueueConnection itibaren realWork için realProcess

      Working::Working()
      {
          working = false;
          connect(this,SIGNAL(realWork()),this,SLOT(realProcess()),Qt::QueuedConnection);
      }
      
  2. Görev sağlayıcınızın tarafında (thread_fast)

    • bir startWork() işaret

      void TaskProv::orderWork()
      {
          emit startWork();
      }
      
    • bir directconnection çalışan işlem yuvasına

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

Bazı notlar:

  • İşlev Working::process() içinde koşulacak thread_fast (Bir işçi üyesi işlevi bile olsa), ancak yalnızca bir bayrağı kontrol eder, bu yüzden işlem süresini etkilememelidir

  • Potansiyel bir ek görev düşüşüne dikkat ederseniz, İşçi'nin çalışma bayrağını daha sıkı yönetim için mutex ile koruyabilirsiniz.

  • Bu, bağlantı türünün Direct ve Queue öğelerinin doğru bir kombinasyonu olması gerektiğinden, lpapp'ın "Veri mutator yuvasından meşgul bir slot işlemi sırası" na çok benzer.


1