c++qtqt5qtimerqeventloop

How to get the value passed from an unrelated signal to a slot?


I have a signal/slot arrangement for an asynchronous call to request data. Normally that's fine as received data just updates controls. But in a couple instances I have to wait for the reply to the request to be sure an action is complete before moving to the next step. There is a signal that is fired every time valid received data is queued.

There is also a need for blocking writes, but the read/write mechanisms are almost identical.

The following code pattern, using a local event loop, is (I believe) a standard pattern for waiting for a signal.

But the signal is sent with extra data, in this case the address and other stuff.

I need to check that the signal was the one associated with the request or I may continue to the next step far too soon.

How do I receive that extra (address) data when the receiving slot does not know how to handle it, as in this case the slot is an event loop (or a timer).

Do I really have to subclass an QEventLoop or is there a simpler way?

The following is a slightly simplified/contrived example.

quint16 System::blockingRead(quint16 address)
{
    QTimer timer;
    timer.setSingleShot(true);
    QEventLoop loop;
    connect( this, &System::readComplete, &loop, &QEventLoop::quit );
    connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );    
    timer.start(3000);
    
    ReadFromSystem(address);  //  Asynchronous call
    
    bool waiting = true;
    bool timeout = false
    do {
        loop.exec();
        waiting = loop.getReceivedAddress() == address;
        timeout = not timer.isActive();
    while(waiting and not timeout);

    if(not timeout) {
        return(loop.getExtraData());
    }
    else {
        throw ECRException();
        qDebug("timeout");
    }
}

Solution

  • Final solution without re-writing the entire thing.

    Subclass QEventLoop, passing the expected address, then add a check on the received queue item address.

    If the expected address turns up in the queue then the loop can quit the event loop and return expected value.

    If the timer expires then we've waited too long so raise an exception and also quit the event loop.

    Existing code emits readComplete(TransportReply *) every time a valid frame is decoded.

    In sys.h

    class SysEventLoop: public QEventLoop
    {
        Q_OBJECT
    
    public:
        SysEventLoop(quint16 address) : QEventLoop(){m_address = address;}
    
    public slots:
        void wait_for_address(TransportReply * reply){
            if(!reply)
            {
                qDebug() << "Waiting: Null reply" << m_address;
                return;
            }
            TransportDataUnit res = reply->result();
            quint16 addr = static_cast<quint16>(res.getAddress());
            if(m_address==addr)
            {
                quit();
                qDebug() << "-----The wait is over" << m_address;
            }
            else qDebug() << "Waiting:" << addr << "!=" << m_address;
        }
    
    
    private:
        quint16 m_address;
    };
    

    in sys.cpp

    quint16 System::blockingRead(quint16 address)
    {
        QTimer timer;
        timer.setSingleShot(true);
        SysEventLoop loop(address);  //Pass the expected address
    
        connect( this, &System::readComplete, &loop, &SysEventLoop::wait_for_address );
        connect( &timer, &QTimer::timeout, &loop, &SysEventLoop::quit );    
        timer.start(3000);
        
        ReadFromSystem(address);  //  Asynchronous call, read request queued.
        
        loop.exec();   //Wait here until the expected address turns up.
        //The UI is not blocked.
        //Reads from addresses we did not want are ignored.
    
        if(not timer.isActive()) {
            return(loop.getExtraData());
        }
        else {
            throw ECRException();
        }
    }