Wait for signal while processing other signals

后端 未结 2 1350
情歌与酒
情歌与酒 2020-12-20 03:32

My Qt application talks to a serial device, and occasionally has to wait for this device to send a byte. To accomplish this, I create a new eventloop that exits as soon as t

相关标签:
2条回答
  • 2020-12-20 03:56

    You're thinking synchronously in a pre-C++1z world. In C++14 (and prior) asynchronous programming, there is mostly no place for a notion of a wait that is implemented as a function that returns when the wait is over (switch-based coroutine hacks excepted). You are also not using the fact that your application is stateful, and the state transitions can be expressed in a state machine.

    Instead, you should simply act on data being available. Presumably, your application can be in multiple states. One of the states - the one where you have to wait for input - is simply exited when the input arrives.

    The example below uses a simple process-local pipe, but it would work exactly the same if you were using a serial port - both are a QIODevice and emit requisite signals. We start with the project file.

    # async-comms-32309737.pro
    QT       += widgets core-private
    TARGET = async-comms-32309737
    CONFIG   += c++11
    TEMPLATE = app
    SOURCES += main.cpp
    

    To make things simple, the pipe implementation reuses the QRingBuffer private class from Qt. See this question for more fleshed-out implementation(s).

    // main.cpp
    #include <QtWidgets>
    #include <private/qringbuffer_p.h>
    
    /// A simple point-to-point intra-application pipe. This class is not thread-safe.
    class AppPipe : public QIODevice {
       Q_OBJECT
       AppPipe * m_other { nullptr };
       QRingBuffer m_buf;
    public:
       AppPipe(AppPipe * other, QObject * parent = 0) : QIODevice(parent), m_other(other) {
          open(QIODevice::ReadWrite);
       }
       void setOther(AppPipe * other) { m_other = other; }
       qint64 writeData(const char * data, qint64 maxSize) Q_DECL_OVERRIDE {
          if (!maxSize) return maxSize;
          m_other->m_buf.append(QByteArray(data, maxSize));
          emit m_other->readyRead();
          return maxSize;
       }
       qint64 readData(char * data, qint64 maxLength) Q_DECL_OVERRIDE {
          return m_buf.read(data, maxLength);
       }
       qint64 bytesAvailable() const Q_DECL_OVERRIDE {
          return m_buf.size() + QIODevice::bytesAvailable();
       }
       bool isSequential() const Q_DECL_OVERRIDE { return true; }
    };
    

    We start with a simple UI, with one button to restart the state machine, another to transmit a single byte that will be received by the client, and a label that indicates the current state of the state machine.

    screenshot of the example

    int main(int argc, char *argv[])
    {
       QApplication a { argc, argv };
       QWidget ui;
       QGridLayout grid { &ui };
       QLabel state;
       QPushButton restart { "Restart" }, transmit { "Transmit" };
       grid.addWidget(&state, 0, 0, 1, 2);
       grid.addWidget(&restart, 1, 0);
       grid.addWidget(&transmit, 1, 1);
       ui.show();
    

    We now create the simulated device and the client pipe endpoints.

       AppPipe device { nullptr };
       AppPipe client { &device };
       device.setOther(&client);
    

    The state machine has three states. The s_init is the initial state, and is exited after a 1.5s delay. The s_wait state is only exited when we receive some data (a byte or more) from the device in that state. In this example, receiving the data in other states has no effect. The machine is set to restart automatically when stopped.

       QStateMachine sm;
       QState
             s_init { &sm },    // Exited after a delay
             s_wait { &sm },    // Waits for data to arrive
             s_end { &sm };     // Final state
       QTimer timer;
       timer.setSingleShot(true);
    
       sm.setInitialState(&s_init);
       QObject::connect(&sm, &QStateMachine::stopped, &sm, &QStateMachine::start);
       QObject::connect(&s_init, &QState::entered, [&]{ timer.start(1500); });
       s_init.addTransition(&timer, SIGNAL(timeout()), &s_wait);
       s_wait.addTransition(&client, SIGNAL(readyRead()), &s_end);
    

    To visualize the state machine's progress, we assign the state label's text property in each of the states:

       s_init.assignProperty(&state, "text", "Waiting for timeout.");
       s_wait.assignProperty(&state, "text", "Waiting for data.");
       s_end.assignProperty(&state, "text", "Done.");
    

    Finally, the restart button stops the state machine - it will self-restart then. The transmit button simulates the device sending one byte of data.

       QObject::connect(&restart, &QPushButton::clicked, &sm, &QStateMachine::stop);
       QObject::connect(&transmit, &QPushButton::clicked, [&]{
          device.write("*", 1);
       });
    

    We start the machine, enter the event loop, and let Qt follow our directions onwards from here. The main.moc file is included for it contains the metadata for AppPipe.

       sm.start();
       return a.exec();
    }
    
    #include "main.moc"
    
    0 讨论(0)
  • 2020-12-20 04:05

    There are several Types of which Signals and Slots can be connected.

    See: http://doc.qt.io/qt-4.8/qt.html#ConnectionType-enum

    Have you tried Qt::DirectConnection: connect(d_serial, SIGNAL(readyRead()), &wait, SLOT(quit()),Qt::DirectConnection); ?

    0 讨论(0)
提交回复
热议问题