阻塞发送方
演示如何在工作线程中使用QSerialPort 的同步 API。
Blocking Sender展示了如何在工作线程中使用QSerialPort 的同步 API 为串行接口创建应用程序。
QSerialPort 阻塞发送器支持两种编程选择:
- 异步(非阻塞)方案。当控件返回 Qt 事件循环时,将安排并执行操作。当操作完成时,QSerialPort 类会发出一个信号。例如,write() 方法会立即返回。当数据发送到串行端口时,QSerialPort 类发出bytesWritten() 信号。
- 同步(阻塞)替代方法。在无头和多线程应用程序中,可以调用 wait 方法(本例中为waitForReadyRead() )来暂停调用线程,直到操作完成。
本例演示了同步替代方法。终端示例说明了异步替代方法。
本例的目的是演示如何简化串行编程代码,同时又不影响用户界面的响应速度。阻塞串行编程 API 通常可以简化代码,但只能在非图形用户界面线程中使用,以保持用户界面的响应性。
此应用程序是发送方,演示了与接收方应用程序搭配工作的阻塞接收示例。
发送方应用程序通过串口向接收方应用程序发起传输请求,并等待响应。
class SenderThread : public QThread { Q_OBJECT public: explicit SenderThread(QObject *parent = nullptr); ~SenderThread(); void transaction(const QString &portName, int waitTimeout, const QString &request); signals: void response(const QString &s); void error(const QString &s); void timeout(const QString &s); private: void run() override; QString m_portName; QString m_request; int m_waitTimeout = 0; QMutex m_mutex; QWaitCondition m_cond; bool m_quit = false; };
SenderThread 是QThread 的子类,提供了向接收方发送请求的调度 API。该类提供响应和报错信号。可以调用 transaction() 方法,用所需请求启动新的发送方事务。结果由 response() 信号提供。如果出现任何问题,则会发出 error() 或 timeout() 信号。
请注意,事务()方法是在主线程中调用的,但请求是在发送线程线程中提供的。SenderThread 的数据成员在不同线程中并发读写,因此使用QMutex 类来同步访问。
void SenderThread::transaction(const QString &portName, int waitTimeout, const QString &request) { const QMutexLocker locker(&m_mutex); m_portName = portName; m_waitTimeout = waitTimeout; m_request = request; if (!isRunning()) start(); else m_cond.wakeOne(); }
transaction() 方法存储串行端口名称、超时和请求数据。可以通过QMutexLocker 锁定互斥来保护这些数据。然后就可以启动线程,除非它已经在运行。wakeOne() 方法将在后面讨论。
void SenderThread::run() { bool currentPortNameChanged = false; m_mutex.lock(); QString currentPortName; if (currentPortName != m_portName) { currentPortName = m_portName; currentPortNameChanged = true; } int currentWaitTimeout = m_waitTimeout; QString currentRequest = m_request; m_mutex.unlock();
在 run() 函数中,首先要锁定QMutex 对象,然后使用成员数据获取串行端口名称、超时和请求数据。完成这些操作后,就会释放QMutex 锁。
在任何情况下,transaction()
方法都不能与获取数据的进程同时调用。请注意,虽然QString 类是可重入的,但它不是线程安全的。因此,不建议在请求线程中读取串行端口名称,而在另一个线程中超时或请求数据。SenderThread 类一次只能处理一个请求。
QSerialPort 对象是在进入循环之前在 run() 方法的堆栈中构建的:
QSerialPort serial; if (currentPortName.isEmpty()) { emit error(tr("No port name specified")); return; } while (!m_quit) {
这使得在运行循环时创建对象成为可能。这也意味着所有对象方法都在 run() 方法的作用域内执行。
在循环中检查当前事务的串行端口名称是否已更改。如果串行端口名称发生变化,则重新打开串行端口,然后重新配置。
if (currentPortNameChanged) { serial.close(); serial.setPortName(currentPortName); if (!serial.open(QIODevice::ReadWrite)) { emit error(tr("Can't open %1, error code %2") .arg(m_portName).arg(serial.error())); return; } }
循环将继续请求数据、向串行端口写入数据并等待所有数据传输完毕。
// write request const QByteArray requestData = currentRequest.toUtf8(); serial.write(requestData); if (serial.waitForBytesWritten(m_waitTimeout)) {
警告 关于阻塞传输,应在每次调用write 方法后使用waitForBytesWritten() 方法。这将处理所有 I/O 例程,而不是 Qt 事件循环。
如果传输数据时发生超时错误,就会发出 timeout() 信号。
} else { emit timeout(tr("Wait write request timeout %1") .arg(QTime::currentTime().toString())); }
请求成功后会有一段等待响应的时间,然后会再次读取数据。
// read response if (serial.waitForReadyRead(currentWaitTimeout)) { QByteArray responseData = serial.readAll(); while (serial.waitForReadyRead(10)) responseData += serial.readAll(); const QString response = QString::fromUtf8(responseData); emit this->response(response);
警告 至于阻塞选项,应在每次调用 read() 之前使用waitForReadyRead() 方法。这将处理所有 I/O 例程,而不是 Qt 事件循环。
如果在接收数据时发生超时错误,就会发出 timeout() 信号。
} else { emit timeout(tr("Wait read response timeout %1") .arg(QTime::currentTime().toString())); }
事务成功完成后,response() 信号将包含从接收方应用程序接收到的数据:
emit this->response(response);
之后,线程进入睡眠状态,直到下一个事务出现。唤醒后,线程使用成员读取新数据,并从头开始循环运行。
m_mutex.lock(); m_cond.wait(&m_mutex); if (currentPortName != m_portName) { currentPortName = m_portName; currentPortNameChanged = true; } else { currentPortNameChanged = false; } currentWaitTimeout = m_waitTimeout; currentRequest = m_request; m_mutex.unlock(); }
运行示例
运行示例 Qt Creator,打开Welcome 模式,并从Examples 中选择示例。更多信息,请参阅Qt Creator: 教程:构建并运行。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.