Qt 4.8:两个信号和来自不同线程的一个插槽之间的连接行为

时间:2016-12-23 10:18:07

标签: c++ multithreading qt

我有三个QThread运行三个事件循环。

在每个线程中,我有一个对象,这些对象有信号和插槽。

  1. ThreadA => ObjectA与theSignalA和theSlotA
  2. ThreadB => ObjectB与theSignalB和theSlotB
  3. ThreadC => ObjectC withSignalB和theSlotB
  4. 我完全理解一个信号和一个插槽之间的连接是如何工作的:

    // This connection would execute theSlotB in ThreadA
    connect(objectA, SIGNAL(theSignalA()), objectB, SLOT(theSlotB()), Qt::DirectConnection);
    
    // This connection would post an event in ThreadB event loop, which will execute theSlotB
    connect(objectA, SIGNAL(theSignalA()), objectB, SLOT(theSlotB()), Qt::QueuedConnection);
    

    我问的是这样的事情的行为:

    auto sig2sig = Qt::DirectConnection;
    auto sig2slot = Qt::DirectConnection;
    connect(objectA, SIGNAL(theSignalA()), objectB, SIGNAL(theSignalB()), sig2sig);
    connect(objectB, SIGNAL(theSignalB()), objectC, SLOT(theSlotC()), sig2slot);
    

    theSlotCsig2sig的不同可能值的执行位置sig2slot

    • sig2sig = DirectConnectionsig2slot= DirectConnection
      • 作为theSignalAtheSignalB之间的DirectConnection?
    • sig2sig = DirectConnectionsig2slot= QueuedConnection
      • 作为theSignalAtheSignalB之间的排队连接?
    • sig2sig = QueuedConnectionsig2slot= DirectConnection
      • theSlotC执行了ThreadB
    • sig2sig = QueuedConnectionsig2slot= QueuedConnection
      • theSlotC执行了ThreadC,但是在重新发出ThreadB信号后发生了延迟?

    或者SIGNAL / SIGNAL连接可能刚被丢弃?

3 个答案:

答案 0 :(得分:2)

信号之间的连接类型确定发出第二个信号的线程,只需将该信号视为执行其连接的插槽的另一个函数/插槽(完全相同{{3}申请):

  • 如果类型为Qt::DirectConnection,则第二个信号始终从发出第一个信号的线程发出。
  • 如果类型为Qt::QueuedConnection,则当控制返回到接收方对象的线程的事件循环时,第二个信号总是排队等待调用。
  • 如果类型为Qt::AutoConnection,则在发出信号并忽略发送对象的线程时,将解析连接类型。
    • 如果接收器对象位于发出第一个信号的同一线程中,则与使用Qt::DirectConnection相同。
    • 否则,这与使用Qt::QueuedConnection
    • 相同

我写了一个最小的测试来证明这件事:

#include <QtCore>

//QThread wrapper for safe destruction
//see http://stackoverflow.com/a/19666329
class Thread : public QThread{
    using QThread::run; //final
public:
    Thread(QObject* parent= nullptr): QThread(parent){}
    ~Thread(){ quit(); wait();}
};

class Worker : public QObject{
    Q_OBJECT
public:
    explicit Worker(QString name, QObject* parent= nullptr):QObject(parent){
        setObjectName(name);
        //the statement is printed from the thread that emits the signal
        //since we don't provide a context object
        connect(this, &Worker::workerSignal, [=]{
            qDebug() << objectName() << "signal emitted from thread:"
                     << QThread::currentThread()->objectName();
        });
    }
    ~Worker() = default;

    Q_SIGNAL void workerSignal();
    Q_SLOT void workerSlot(){
        qDebug() << objectName() << "slot invoked in thread:"
                 << QThread::currentThread()->objectName();
    }
};

int main(int argc, char* argv[]){
    QCoreApplication a(argc, argv);

    //using the main thread as threadA
    QThread::currentThread()->setObjectName("threadA");
    Worker workerA("workerA");
    //creating threadB and threadC
    Thread threadB;
    threadB.setObjectName("threadB");
    Worker workerB("workerB");
    workerB.moveToThread(&threadB);
    Thread threadC;
    threadC.setObjectName("threadC");
    Worker workerC("workerC");
    workerC.moveToThread(&threadC);
    threadB.start(); threadC.start();

    //change the following types to whatever case you want to test:
    auto sig2sig= Qt::QueuedConnection;
    auto sig2slot= Qt::QueuedConnection;
    qDebug() << "sig2sig= " << sig2sig << ", sig2slot=" << sig2slot;
    QObject::connect(&workerA, &Worker::workerSignal,
                     &workerB, &Worker::workerSignal, sig2sig);
    QObject::connect(&workerB, &Worker::workerSignal,
                     &workerC, &Worker::workerSlot, sig2slot);
    emit workerA.workerSignal();

    //quit application after 0.5 second
    QTimer::singleShot(500, &a, &QCoreApplication::quit);
    return a.exec();
}

#include "main.moc"

这将按如下方式设置连接:

workerA::workerSignal() -------> workerB::workerSignal() -------> workerC::workerSlot()

每个工作人员都在自己的线程中,您可以通过更改分配给sig2sigsig2slot变量的值来更改连接类型。以下是您要求的案例中的输出:

  • sig2sig = DirectConnectionsig2slot= DirectConnection

    一切都在threadA中作为直接函数调用执行。

    "workerA" signal emitted from thread: "threadA"
    "workerB" signal emitted from thread: "threadA"
    "workerC" slot invoked in thread: "threadA"
    
  • sig2sig = DirectConnectionsig2slot= QueuedConnection

    信号在threadA中作为直接函数调用执行。该插槽在threadC中调用。

    "workerA" signal emitted from thread: "threadA"
    "workerB" signal emitted from thread: "threadA"
    "workerC" slot invoked in thread: "threadC"
    
  • sig2sig = QueuedConnectionsig2slot= DirectConnection

    信号排队,并从threadB发出。该插槽在threadB中作为直接函数调用调用。

    "workerA" signal emitted from thread: "threadA"
    "workerB" signal emitted from thread: "threadB"
    "workerC" slot invoked in thread: "threadB"
    
  • sig2sig = QueuedConnectionsig2slot= QueuedConnection

    信号排队,并从threadB发出。插槽调用也排队并在threadC中执行。所以,每件事都发生在线程中,如果使用Qt::AutoConnection,这将是相同的行为:

    "workerA" signal emitted from thread: "threadA"
    "workerB" signal emitted from thread: "threadB"
    "workerC" slot invoked in thread: "threadC"
    

答案 1 :(得分:0)

在qt中,信号只是另一个函数,当被调用时,通过所请求的方法调用插槽,即

  • DirectConnection - &gt;在当前线程中调用的插槽,
  • QueuedConnection - &gt;复制参数,并在下次进入事件循环时在目标线程中调用插槽,
  • 自动连接 - &gt;如果在目标插槽的线程中发出信号,则直接调用slot,否则排队。

当您将信号作为插槽连接时,它的行为类似于插槽,即通过请求的方法调用/发出。请注意,实际发射是线程安全的,因此只要通过AutoConnection或QueuedConnection连接插槽,就可以从任何线程直接调用/发出信号(参见Signals and Slots Across Threads)。

一些例子:

sig2sig = DirectConnection,sig2slot = DirectConnection

当前线程(A)中调用的所有内容。 slot需要是线程安全的。

sig2sig = DirectConnection,sig2slot = QueuedConnection

signalB在线程A中发出,slotC正确排队以在其自己的线程中执行 与sig2sig = DirectConnection相同,sig2slot = AutoConnection

sig2sig = QueuedConnection,sig2slot = QueuedConnection

signalB排队在线程B中发出。当发生这种情况时,slotC排队等待在线程C中执行 与sig2sig = AutoConnection相同,sig2slot = AutoConnection。

结论

由于排队连接比直接连接慢得多,如果这是一个瓶颈,最好将sig2sig与DirectConnection连接,并将DirectConnections连接到signalB必须是线程安全的。

但是,如果只有两个线程,则应使用AutoConnection。然后只能有一个排队呼叫,另一个是直接呼叫。

答案 2 :(得分:0)

如果你看一下In MOC的对象,你会看到每个信号都有一个信号处理方法,它会激活(比如执行或发送事件)连接的插槽,当你发出一个信号时会调用这个方法,所以发射实际上是在线程中调用该对象的方法。实际上,信号处理程序只是另一个插槽。在信号到信号连接的情况下,将在调用线程中调用第一对象的信号处理程序并激活连接线程中的槽(即第二对象的信号处理程序)。激活的时隙可以是目标对象的时隙或其信号之一。激活确实关心发射器信号和目标插槽/信号之间的连接类型。当您将信号连接到另一个信号时,实际上您正在将信号连接到第二个对象的信号处理程序。

在DirectConnection中,将在发射器线程中调用插槽。 在QueuedConnection中,将在目标对象的线程中调用插槽。

因此,如果您将目标信号想象为另一个发出目标信号的插槽,我们将会:

  • sig2sig = Direct,sig2slot = Direct

    • SignalA和SignalB之间的连接是直接的,所以ObjectB的信号处理程序将在ObjectA的线程中执行,SignalB和SlotC之间的连接也是直接的,所以SlotC将在发射器线程中执行,这是ObjectA的线程
  • sig2sig = Direct,sig2slot = Queued

    • SignalA和SignalB之间有直接连接,ObjectB的信号处理程序将在ObjectA的线程中执行,但SignalB和SlotC之间的连接排队,因此SlotC将在ObjectC的线程中执行
  • sig2sig =已排队,sig2slot =直接

    • SignalA和SignalB之间存在排队连接,因此SignalA的发射将执行ObjectB的信号处理程序int ObjectB的线程,现在ObjectB的信号处理程序将使用Direct方案激活SlotC,因此SlotC将在其发射器线程中执行是ObjectB的线程(线程B)。
  • sig2Sig =已排队,sig2slot =已排队

    • SignalA和SignalB以及SignalB和SlotC之间存在排队连接,因此通过发出SignalA,ObjectB的线程(ThreadB)将调用ObjectB的信号处理程序,并发出在ObjectC的线程(ThreadC)中调用SlotC的SignalB < / LI>

此代码还验证了行为

#include <QObject>
#include <QDebug>
#include <QThread>

class XClass : public QObject
{
    Q_OBJECT

public:
    XClass(char ident){  this->ident=ident; this->moveToThread(&this->t); this->t.start(); }
    char ident;
private:
        QThread t;
signals:
    void signalX();

public slots:
    void printTid() { qDebug() << "Object " << this->ident << " lives in thread" << QThread::currentThreadId(); }
    void slotX() { qDebug() << "Slot" << this->ident << " fired in thread" << QThread::currentThreadId(); }
    void emitSignalX(){ qDebug() << "Signal" << this->ident << "emitng from thread" << QThread::currentThreadId(); emit signalX(); }
};



int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    XClass A('A');
    XClass B('B');
    XClass C('C');

    QMetaObject::invokeMethod(&A, "printTid", Qt::BlockingQueuedConnection);
    QMetaObject::invokeMethod(&B, "printTid", Qt::BlockingQueuedConnection);
    QMetaObject::invokeMethod(&C, "printTid", Qt::BlockingQueuedConnection);

    QObject::connect(&A, &XClass::signalX, &B, &XClass::signalX, Qt::DirectConnection);
    QObject::connect(&B, &XClass::signalX, &C, &XClass::slotX, Qt::DirectConnection);

    //QObject::connect(&A, &XClass::signalX, &B, &XClass::signalX, Qt::DirectConnection);
    //QObject::connect(&B, &XClass::signalX, &C, &XClass::slotX, Qt::QueuedConnection);

    //QObject::connect(&A, &XClass::signalX, &B, &XClass::signalX, Qt::DirectConnection);
    //QObject::connect(&B, &XClass::signalX, &C, &XClass::slotX, Qt::QueuedConnection);

    //QObject::connect(&A, &XClass::signalX, &B, &XClass::signalX, Qt::QueuedConnection);
    //QObject::connect(&B, &XClass::signalX, &C, &XClass::slotX, Qt::QueuedConnection);

    QMetaObject::invokeMethod(&A, "emitSignalX");

    return a.exec();
}