处理其他信号时等待信号

Jor*_*eit 1 c++ qt

我的Qt应用程序与串行设备通信,偶尔必须等待此设备发送一个字节.为了实现这一点,我创建了一个新的eventloop,一旦串行缓冲区中有可用信息就会退出:

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]);
}
Run Code Online (Sandbox Code Playgroud)

现在的问题是,当应用程序在等待时,即在eventloop运行时,我需要能够在GUI中按下按钮时与串行设备进行通信.天真地,我尝试将信号连接到执行此操作的插槽,但我发现插槽仅 eventloop终止执行.

我试着,没有任何运气,有一个单独的QThread运行调用qApp->processEvents()无限循环,当eventloop终止时终止.这没用,我不太清楚为什么不呢.解决这个问题的规范方法是什么?

Rei*_*ica 7

你在C++之前的世界中同步思考.在C++ 14(和)之前异步编程,存在多为的一个概念没有地方wait是当等待已经结束(返回的函数实现的switch基于协程 黑客 除外).您也没有使用应用程序是有状态的事实,并且状态转换可以在状态机中表示.

相反,您应该只对可用的数据进行操作.据推测,您的应用程序可能处于多个状态.其中一个状态 - 您必须等待输入的状态 - 只在输入到达时退出.

下面的示例使用一个简单的进程本地管道,但如果您使用串行端口它将完全相同 - 两者都是一个QIODevice并发出必要的信号.我们从项目文件开始.

# async-comms-32309737.pro
QT       += widgets core-private
TARGET = async-comms-32309737
CONFIG   += c++11
TEMPLATE = app
SOURCES += main.cpp
Run Code Online (Sandbox Code Playgroud)

为简单起见,管道实现重用了QRingBufferQt中的私有类.有关更多充实的实现,请参阅此问题.

// 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; }
};
Run Code Online (Sandbox Code Playgroud)

我们从一个简单的UI开始,一个按钮用于重新启动状态机,另一个按钮用于传输将由客户端接收的单个字节,以及一个指示状态机当前状态的标签.

该示例的屏幕截图

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();
Run Code Online (Sandbox Code Playgroud)

我们现在创建模拟设备和客户端管道端点.

   AppPipe device { nullptr };
   AppPipe client { &device };
   device.setOther(&client);
Run Code Online (Sandbox Code Playgroud)

状态机有三种状态.这s_init是初始状态,并在1.5秒延迟后退出.的s_wait,当我们从设备接收一些数据(一个字节或更多)状态仅退出在该状态下.在此示例中,接收其他状态的数据无效.机器设置为在停止时自动重启.

   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);
Run Code Online (Sandbox Code Playgroud)

为了可视化状态机的进度,我们在每个状态中分配state标签的text属性:

   s_init.assignProperty(&state, "text", "Waiting for timeout.");
   s_wait.assignProperty(&state, "text", "Waiting for data.");
   s_end.assignProperty(&state, "text", "Done.");
Run Code Online (Sandbox Code Playgroud)

最后,restart按钮停止状态机 - 它将自动重启.该transmit按钮模拟设备发送一个字节的数据.

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

我们启动机器,进入事件循环,让Qt从这里开始遵循我们的指示.main.moc包含的文件包含元数据AppPipe.

   sm.start();
   return a.exec();
}

#include "main.moc"
Run Code Online (Sandbox Code Playgroud)