Qt5: How to wait for a signal in a thread?
Asked Answered
L

5

30

Probably the title question is not very explicit. I am using Qt5 on Windows7.

In a thread (QThread) at some point, in the "process()" function/method, I must wait for the "encrypted()" SIGNAL belonging to a QSslSocket I am using in this thread. Also I suppose I should use a QTimer and wait for a "timeout()" SIGNAL in order to avoid getting blocked in an infinite loop...
What I have now is:

// start processing data
void Worker::process()
{
    status = 0;
    connect(sslSocket, SIGNAL(encrypted()), this, SLOT(encryptionStarted()));
    QTimer timer;
    connect(&timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
    timer.start(10000);
    while(status == 0)
    {
        QThread::msleep(5);
    }

    qDebug("Ok, exited loop!");

    // other_things here
    // .................
    // end other_things

    emit finished();
}

// slot (for timer)
void Worker::timerTimeout()
{
    status = 1;
}

// slot (for SSL socket encryption ready)
void Worker::encryptionStarted()
{
    status = 2;
}

Well, obviously it doesn't work. It stays in that while-loop forever...
So, the question is: Is there a way to solve this problem? How can I wait for that "encrypted()" SIGNAL but not more than - let's say 10 seconds - in order to avoid getting stuck in that waiting-loop/thread?

Libration answered 11/7, 2015 at 15:18 Comment(0)
L
83

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.

Laundes answered 12/7, 2015 at 4:22 Comment(7)
Very good idea. I tested it (with some modifications to suit my code & purpose) and it works ok. I voted up of course:)Repress
Simple and concise. Bravo.Pepillo
Using event-loop this way will result in "busy wait", right?Bearcat
@Bearcat No. While waiting all events in the main thread are processed and GUI works normal. But this kind of coding is not good practice and it' s recommended to use async methods.Laundes
How to catch signal arguments in this fashion? For example if the encrypted signal had a QVariant payload?Awake
@Awake You can simply connect the encrypted signal to a lambda with the argument and call quit method of event loop in the lambda (a reference to the event loop object should be captured in lambda).Laundes
Do I need to worry about the disconnect issue? For both lambda and quit ways.Extern
H
12

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
Hostetter answered 24/7, 2020 at 3:44 Comment(1)
Note that this class is contained in the Qt::Test module.Dreg
M
9

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.

Marinetti answered 13/7, 2015 at 16:54 Comment(9)
Very interesting approach! I will test it tomorrow (after adapting it to fit my code) and I will try to provide the related feedback :) Hmmm, just great to have a status-machine inside a thread! Sorry I am so enthusiastic, but I am novice in Qt, so each time I see something new (for me) I realize how huge possibilities Qt/C++ can offer...! I wish I can see more examples like this! What can I say? Bravo, excellent idea!Repress
Can you expand on this a bit more: "Note the absence of explicit memory management. Use of owning pointers to Qt classes is a premature optimization and should be avoided where unnecessary."Preparedness
@Preparedness Manual memory management is where you new something and assign it to a raw C pointer, and then have to manually delete it. The code doesn't even have explicit memory allocation with new, but if it had them, the results would be immediately either assigned to smart pointers or made children in a QObject tree. Note that a QObject acts as a smart pointer collection for other QObjects.Biconcave
@Preparedness Lots of bad Qt examples, including those bundled with Qt, use completely unnecessary explicit heap allocations. Since every significant Qt class allocates a PIMPL, you're doubling the number of allocations by putting what amounts to PIMPL pointers on the heap. A QObject and QWidget have the size of ~4-6 pointers even if their PIMPL is two orders of magnitude larger. If you add children in constructors, simply add them as regular (non-pointer) members to the class or its PIMPL.Biconcave
Thanks. I wasn't sure whether you meant QObject-based memory management or smart pointers.Preparedness
@Preparedness Basically, with modern C++, you're supposed to let the compiler and the libraries do the memory management for you. If you do it manually and it's not wrapped in usual RAII facades, you're doing it wrong :)Biconcave
On StackOverflow, not answering the asked question is an anti-pattern. Lacking context, one should give benefit of the doubt and not assume that ignorance somehow bestows insight. More to the point, the if the question asked is indeed an anti-pattern, then pthread_wait_cond must necessarily also be an anti-pattern!Bagman
@Bagman pthread_wait_cond must necessarily also be an anti-pattern Who said it isn't? It's an API meant to be used by libraries as a fast building block. It's not something anyone should be calling directly since they invariably waste a thread that should be doing stuff. Various event queues solve this well, and reinventing the wheel is an antipattern - and oh so pervasive.Biconcave
Nobody appointed you an authority on what is and what is not an antipattern. Let's all please try giving benefit of the doubt, rather than assume the asker was incompetent.Bagman
L
4

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 :)

Libration answered 17/7, 2015 at 16:55 Comment(0)
G
1

QSignalSpy is the most clean way

To see the how you can take a look to this https://doc.qt.io/qt-6/qsignalspy.html

Glassblowing answered 3/5, 2023 at 23:1 Comment(2)
showing an example could improve the long-term value of this post.Fossorial
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Uxorious

© 2022 - 2024 — McMap. All rights reserved.