我的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终止时终止。这不起作用,我不太清楚为什么不这样做。解决这个问题的规范方法是什么?
答案 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开始,一个按钮用于重新启动状态机,另一个用于传输将由客户端接收的单个字节,以及一个标签,指示状态机的当前状态。
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);
?