在处理其他信号时等待信号

时间:2015-08-31 11:02:10

标签: 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]);
}

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

我试着运行一个单独的QThread运行,在无限循环中调用qApp->processEvents(),在eventloop终止时终止。这不起作用,我不太清楚为什么不这样做。解决这个问题的规范方法是什么?

2 个答案:

答案 0 :(得分:7)

你在C ++之前的世界中同步思考。在C ++ 14(和之前的)异步编程中,wait的概念几乎没有地方可以实现为在等待结束时返回的函数(switch - 基于{{ 3}} coroutine hacks)。您也没有使用应用程序是有状态的事实,并且状态转换可以在状态机中表示。

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

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

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

为简单起见,管道实现重用了Qt中的QRingBuffer私有类。有关更多充实的实现,请参阅excepted

// 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; }
};

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

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();

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

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

状态机有三种状态。 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);

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

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

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

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

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

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

#include "main.moc"

答案 1 :(得分:-2)

有几种类型的信号和插槽可以连接。

请参阅:http://doc.qt.io/qt-4.8/qt.html#ConnectionType-enum

您是否尝试过 Qt :: DirectConnection connect(d_serial, SIGNAL(readyRead()), &wait, SLOT(quit()),Qt::DirectConnection);