我的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终止时终止.这没用,我不太清楚为什么不呢.解决这个问题的规范方法是什么?
你在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)