1

I'm looking for a simple way to wait for an object to emit signal with timeout management using Qt.

Is there an easy way to do that using Qt classes?

Here is an example of application:

QLowEnergyController controller(remoteDevice);
controller.connectToDevice();
// now wait for controller to emit connected() with a 1sec timeout
jpo38
  • 20,821
  • 10
  • 70
  • 151

2 Answers2

3

Based on this post, here is a class (encapsulating @EnOpenUK solution) and proposing a wait function with timeout management.

Header file:

#include <QEventLoop>
class WaitForSignalHelper : public QObject
{
    Q_OBJECT
public:
    WaitForSignalHelper( QObject& object, const char* signal );

    // return false if signal wait timed-out
    bool wait();

public slots:
    void timeout( int timeoutMs );

private:
    bool m_bTimeout;
    QEventLoop m_eventLoop;
};

Implementation file:

#include <QTimer>
WaitForSignalHelper::WaitForSignalHelper( QObject& object, const char* signal ) : 
    m_bTimeout( false )
{
    connect(&object, signal, &m_eventLoop, SLOT(quit()));
}

bool WaitForSignalHelper::wait( int timeoutMs )
{
    QTimer timeoutHelper;
    if ( timeoutMs != 0 ) // manage timeout
    {
        timeoutHelper.setInterval( timeoutMs );
        timeoutHelper.start();
        connect(&timeoutHelper, SIGNAL(timeout()), this, SLOT(timeout()));
    }
    // else, wait for ever!

    m_bTimeout = false;

    m_eventLoop.exec();

    return !m_bTimeout;
}

void WaitForSignalHelper::timeout()
{
    m_bTimeout = true;
    m_eventLoop.quit();
}

Example:

QLowEnergyController controller(remoteDevice);
controller.connectToDevice();
WaitForSignalHelper helper( controller, SIGNAL(connected()) );
if ( helper.wait( 1000 ) )
    std::cout << "Signal was received" << std::endl; 
else
    std::cout << "Signal was not received after 1sec" << std::endl;

Note that setting timeout parameter to 0 makes the object wait for ever...could be useful.

Community
  • 1
  • 1
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • 1
    Just be *very* careful at using a nested `QEventLoop`. Your code may reenter through totally unexpected paths. You should be using the (async) signals of `QLowEnergyController`. – peppe Jun 28 '15 at 20:40
  • Yeah, I can confirm peppe's warning. This happened to me in a most WTF'ish way possible, be very careful – Zeks Jun 28 '15 at 21:43
  • @peppe: Thanks for the comment. Could you please elaborate? What do you mean by the using the (async) signals of `QLowEnergyController`. Isn't that what I'm doing? – jpo38 Jun 29 '15 at 06:14
  • You're using them, but you're then somehow forcing a "synchronous" behaviour by using `QEventLoop`. The danger, as I wrote before, is that in that loop something else gets triggered (a timer, a network socket, etc.), which will execute unexpected code paths. The better approach is simply 1) hooking up the signals 2) request the async operation 3) simply return. Let the control flow get back to the main event loop. The signals will then get called when something happens (your connection succeeds, for instance). – peppe Jun 29 '15 at 06:32
  • Got it....so this should only be used in cases where you want to make the flow synchronous (to provide user a synchronous feedback) and when you are sure nothing else will happen during the wait procedure. – jpo38 Jun 29 '15 at 07:44
2

In Qt 5, the QtTest header has QSignalSpy::wait, to wait until a signal is emitted or a timeout (in milliseconds) occurs.

auto controller = QLowEnergyController{remoteDevice};
auto spy = QSignalSpy{*controller, SIGNAL(connected())};
controller.connectToDevice();
spy.wait(1000);
ForgottenUmbrella
  • 1,052
  • 14
  • 14