Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

c++ - Wait for signal while processing other signals

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 there is information available in the serial buffer:

unsigned char MyClass::waitForDevice(int timeout)
{
    QEventLoop wait;
    connect(d_serial, SIGNAL(readyRead()), &wait, SLOT(quit()));
    if (timeout > 0)
        QTimer::singleShot(timeout, &wait, SLOT(quit()));

    wait.exec();
    return static_cast<unsigned char>(d_serial->read(1)[0]);
}

Now the problem is that, while the application is waiting, i.e. while the eventloop is running, I need to be able to communicate to the serial device when a button is pressed in the GUI. Naively, I tried connecting a signal to a slot that does this, but I found that the slot is only executed after the eventloop is terminated.

I tried, without any luck, to have a seperate QThread running that calls qApp->processEvents() in an infinite loop, which is terminated when the eventloop is terminated. This didn't work, and I'm not quite sure why not. What is the canonical way to resolve this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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"

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...