Qt5: How to wait for a signal in a thread?

Starting in Qt 5.0, QSignalSpy offers a wait method. You hook it up to a signal and wait() will block until the signal fires.

QSignalSpy spy(SIGNAL(encrypted()));
spy.wait(5000);  //wait until signal fires or 5 second timeout expires

You can use a local event loop to wait for the signal to be emitted :

QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect( sslSocket, &QSslSocket::encrypted, &loop, &QEventLoop::quit );
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
timer.start(msTimeout);
loop.exec();

if(timer.isActive())
    qDebug("encrypted");
else
    qDebug("timeout");

Here it waits until encrypted is emitted or the the timeout reaches.


In asynchronous programming, the "wait for" is considered an anti-pattern. Instead of waiting for things, design the code to react to a condition becoming fulfilled. E.g., connect the code to a signal.

One way of implementing this is to slice your actions into separate states, and do some work when each of the states is entered. Of course if the amount of work is non-trivial, use a separate slot instead of a lambda to keep things readable.

Note the absence of explicit memory management. Use of owning pointers to Qt classes is a premature optimization and should be avoided where unnecessary. The objects can be direct members of the Worker (or its PIMPL).

The sub-objects must be all a part of the ownership hierarchy that has Worker at the root. That way, you can safely move the Worker instance to another thread, and the objects it uses will follow it. Of course you could also instantiate the Worker in the correct thread - there's a simple idiom for that. The thread's event dispatcher owns the worker, thus when the thread's event loop quits (i.e. after invoking QThread::quit()), the worker will be automatically disposed and no resources will leak.

template <typename Obj>
void instantiateInThread(QThread * thread) {
  Q_ASSERT(thread);
  QObject * dispatcher = thread->eventDispatcher();
  Q_ASSERT(dispatcher); // the thread must have an event loop
  QTimer::singleShot(0, dispatcher, [dispatcher](){
    // this happens in the given thread
    new Obj(dispatcher);
  });
}

The Worker's implementation:

class Worker : public QObject {
  Q_OBJECT
  QSslSocket sslSocket;
  QTimer timer;
  QStateMachine machine;
  QState s1, s2, s3;
  Q_SIGNAL void finished();
public:
  explicit Worker(QObject * parent = {}) : QObject(parent),
    sslSocket(this), timer(this), machine(this),
    s1(&machine), s2(&machine), s3(&machine) {
    timer.setSingleShot(true);
    s1.addTransition(&sslSocket, SIGNAL(encrypted()), &s2);
    s1.addTransition(&timer, SIGNAL(timeout()), &s3);
    connect(&s1, &QState::entered, [this]{
      // connect the socket here
      ...
      timer.start(10000);
    });
    connect(&s2, &QState::entered, [this]{
      // other_things here
      ...
      // end other_things
      emit finished();
    });
    machine.setInitialState(&s1);
    machine.start();
  }
};

Then:

void waitForEventDispatcher(QThread * thread) {
  while (thread->isRunning() && !thread->eventDispatcher())
    QThread::yieldCurrentThread();
}

int main(int argc, char ** argv) {
  QCoreApplication app{argc, argv};
  struct _ : QThread { ~Thread() { quit(); wait(); } thread;
  thread.start();
  waitForEventDispatcher(&thread);
  instantiateInThread<Worker>(&myThread);
  ...
  return app.exec();
}

Note that connecting to QThread::started() would be racy: the event dispatcher doesn't exist until some code within QThread::run() had a chance to execute. Thus we have to wait for the thread to get there by yielding - this is very likely to get the worker thread to progress far enough within one or two yields. Thus it won't waste much time.


I had some time these days and I did some investigation...
Well, I browsed "http://doc.qt.io/qt-5/qsslsocket.html" and found this:

bool QSslSocket::waitForEncrypted(int msecs = 30000)

To my real shame, I didn't noticed it before... :(
Definitely need to buy some glasses (unfortunately, it's not a joke!)
I am willing to modify my code accordingly in order to test it (on Monday @ office).
Pretty much chances that it'll work.
[Late edit]:
Yes, this is the solution I implemented in my final code and it works well, so I decided to share :)