1
votes

I have three QThread running three Events loops.

In each thread, I have one object, and these object have signals and slots.

  1. ThreadA => ObjectA with theSignalA and theSlotA
  2. ThreadB => ObjectB with theSignalB and theSlotB
  3. ThreadC => ObjectC with theSignalB and theSlotB

I understand perfectly how works connections between one signal and one slots :

// 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);

What I ask is the behavior of things like that :

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

Where (and when) is executed theSlotC for the different possible values of sig2sig and sig2slot.

  • sig2sig = DirectConnection, sig2slot= DirectConnection,
    • As DirectConnection between theSignalA and theSignalB ?
  • sig2sig = DirectConnection, sig2slot= QueuedConnection,
    • As QueuedConnection between theSignalA and theSignalB ?
  • sig2sig = QueuedConnection, sig2slot= DirectConnection,
    • Is theSlotC executed in ThreadB ?
  • sig2sig = QueuedConnection, sig2slot= QueuedConnection,
    • Is theSlotC executed in ThreadC, but after a delay from re-emitting the signal from ThreadB ?

Or perhaps the SIGNAL/SIGNAL connection is just discarded ?

3
There is absolutely no difference between the targets of a connection: they are just a method. Whether that method is written by you, or written by the moc is of no consequence. The behavior is the same. You should also note that there are very few reasons not to use the automatic connection type.Kuba hasn't forgotten Monica
Can you explicit the very few reasons ?Guillaume Fouillet
You might want to force a queued connection if you intend the target to be invoked from the event loop even if it has the same thread context. You might want to force a direct connection to a target living in a different thread if the target method is thread-safe. You might have a rare need for a blocking queued connection. That's about it.Kuba hasn't forgotten Monica

3 Answers

2
votes

The connection type between signals determines the thread where the second signal is emitted, just think of the signal as another function/slot that executes slots it is connected to (the exact same rules apply):

  • If the type is Qt::DirectConnection, the second signal is always emitted from the thread that emitted the first signal.
  • if the type is Qt::QueuedConnection, the second signal is always queued to be invoked when control returns to the event loop of the receiver object's thread.
  • If the type is Qt::AutoConnection, the connection type is resolved when the signal is emitted and the thread of the sending object is ignored.
    • If the receiver object lives in the same thread where the first signal is emitted, this will be the same as using Qt::DirectConnection.
    • Otherwise, this will be the same as using Qt::QueuedConnection.

I wrote a minimal test to demonstrate this thing:

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

this will setup connections as follows:

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

Each worker lives in its own thread, and you can change the connection types by changing the values assigned to sig2sig and sig2slot variables. Here is the output in the cases you asked for:

  • sig2sig = DirectConnection, sig2slot= DirectConnection:

    Everything is executed in threadA as direct function calls.

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

    The signal is executed in threadA as a direct function call. The slot is invoked in threadC.

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

    The signal is queued and its gets emitted from threadB. The slot is called in threadB as a direct function call.

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

    The signal is queued and its gets emitted from threadB. The slot invocation is also queued and gets executed in threadC. So, every thing happens in the right thread, this would be the same behavior if Qt::AutoConnection is used:

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

In qt, a signal is just another function that, when called, calls the slots via the requested method, i.e.

  • DirectConnection -> slot called in current thread,
  • QueuedConnection -> arguments are copied, and slot called in destination thread next time it enters the event loop,
  • AutoConnection -> slot called directly direct if signal emmitted in the destination slot's thread, queued otherwise.

When you connect a signal as a slot, it behaves like a slot, i.e. gets called/emitted via the requested method. Note that the actual emission is thread safe, so calling/emitting a signal directly is fine from any thread as long as slots are connected via AutoConnection or QueuedConnection (see Signals and Slots Across Threads).

Some examples:

sig2sig = DirectConnection, sig2slot= DirectConnection

Everything called in current thread (A). slot needs to be thread safe.

sig2sig = DirectConnection, sig2slot= QueuedConnection

signalB is emitted in thread A, slotC is correctly queued to execute in its own thread.
Same as sig2sig = DirectConnection, sig2slot= AutoConnection

sig2sig = QueuedConnection, sig2slot= QueuedConnection

signalB is queued to be emitted in thread B. When that happens, slotC is queued to execute in thread C.
Same as sig2sig = AutoConnection, sig2slot= AutoConnection.

Conclusion

Since queued connections are so much slower than direct, if this is a bottleneck, it can be better to connect sig2sig with a DirectConnection and document that DirectConnections to signalB must be threadsafe.

However, if there are only two threads, you should use AutoConnection. Then there can be only one queued call, and the other will be direct.

0
votes

If you look at In MOC'ed object, you will see there is a signal handler method for each signal which will activate (say execute or send event) the connected slot, this method will be called when you emit a signal, so the emission actually calls a method of that object in thread which is emited. Actually the is signal handler is just another slot. In case of signal to signal connection the signal handler of first object will be called in the calling thread and activates the slot (say signal handler of second object) in connected thread. The activated slot could be a slot of destination object or one of its signals. The activation does care about connection type between the emiter signal and destination slot/signal. When you connect a signal to another signal, actually you're connecting a signal to second object's signal handler.

In DirectConnection the slot will be called in emiter thread. In QueuedConnection the slot will be called in destination object's thread.

So if you imagine a destination signal as an another slot which emits the destination signal actually we will have:

  • sig2sig=Direct, sig2slot=Direct

    • Connection between SignalA and SignalB is direct, so ObjectB's signal handler will be executed in ObjectA's thread, also connection between SignalB and SlotC is direct, so the SlotC will be executed in emiter thread which is ObjectA's thread
  • sig2sig=Direct, sig2slot=Queued

    • There is direct connection between SignalA and SignalB, ObjectB's signal handler will be executed in ObjectA's thread, but the connection between SignalB and SlotC is queued, so the SlotC will be executed in ObjectC's thread
  • sig2sig=Queued, sig2slot=Direct

    • There is queued connection between SignalA and SignalB, so the emission of SignalA will execute ObjectB's signal handler int ObjectB's thread, now the ObjectB's signal handler will activate the SlotC with Direct scheme, so the SlotC will be executed in its emitter thread which is ObjectB's thread (Thread B).
  • sig2Sig=Queued, sig2slot=Queued

    • There is queued connection between SignalA and SignalB, and also SignalB and SlotC so by emiting SignalA, ObjectB's signal handler will be called in ObjectB's thread (ThreadB) and it emits the SignalB which calls the SlotC in ObjectC's thread (ThreadC)

This code also verifies the behavior

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