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

160 views Asked by At

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");
    }
}
2

There are 2 answers

0
Jay M On BEST ANSWER

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();
    }
}
2
scopchanov On

Your code seems totally wrong to me, which makes me think, that you have a XY problem. So I am going to leave the code alone and instead try to help you solve your actual probem.

Let me start with what I consider a fundamental misunderstanding of how signals and slots should be used, as it is important for finding a suitable solution:

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

There is no such thing like unrelated signal. If you connect a slot to a signal you make them related.

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

You do not wait for a signal. You connect a slot to it to handle the event of it being emitted.

  1. I have to wait for the reply to the request to be sure an action is complete before moving to the next step.

No, you don't have to wait. Keep a state and react accordingly.

Now that last thing is what I believe you actually need in your appliaction. Define an enum with the possible states according to your communication protocol and a variable, e.g. m_state, of this type. Then update the m_state in the slot, which is connected to the signal that is fired every time valid received data is queued. In this very slot check the m_state and use a switch to take one or another action, depending on its value. It is a good idea to prepare a different methods for each one of these one or another action.