Reputation: 555
I have three QThread running three Events loops.
In each thread, I have one object, and these object have signals and slots.
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
,
theSignalA
and theSignalB
?sig2sig = DirectConnection
, sig2slot= QueuedConnection
,
theSignalA
and theSignalB
?sig2sig = QueuedConnection
, sig2slot= DirectConnection
,
theSlotC
executed in ThreadB
?sig2sig = QueuedConnection
, sig2slot= QueuedConnection
,
theSlotC
executed in ThreadC
, but after a delay from re-emitting the signal from ThreadB
?Or perhaps the SIGNAL/SIGNAL connection is just discarded ?
Upvotes: 1
Views: 606
Reputation: 8355
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):
Qt::DirectConnection
, the second signal is always emitted from the thread that emitted the first signal.Qt::QueuedConnection
, the second signal is always queued to be invoked when control returns to the event loop of the receiver object's thread.Qt::AutoConnection
, the connection type is resolved when the signal is emitted and the thread of the sending object is ignored.
Qt::DirectConnection
.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"
Upvotes: 2
Reputation: 1763
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
sig2sig=Direct, sig2slot=Queued
sig2sig=Queued, sig2slot=Direct
sig2Sig=Queued, sig2slot=Queued
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();
}
Upvotes: 0
Reputation: 2515
In qt, a signal is just another function that, when called, calls the slots via the requested method, i.e.
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:
Everything called in current thread (A). slot needs to be thread safe.
signalB is emitted in thread A, slotC is correctly queued to execute in its own thread.
Same as sig2sig = DirectConnection, sig2slot= AutoConnection
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.
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.
Upvotes: 0