阻塞接收器
演示如何在非图形用户界面线程中使用QSerialPort 的同步 API。
Blocking Receiver演示如何在非图形用户界面线程中使用QSerialPort 的同步 API 为串行接口创建应用程序。
QSerialPort Blocking Receiver 支持两种一般的编程方法:
- 异步(非阻塞)方法。当控件返回 Qt 的事件循环时,会安排并执行操作。当操作完成时,QSerialPort 会发出一个信号。例如,QSerialPort::write() 会立即返回。当数据发送到串行端口时,QSerialPort 会发出bytesWritten() 信号。
- 同步(阻塞)方法。在非图形用户界面和多线程应用程序中,可以调用
waitFor...()
函数(即QSerialPort::waitForReadyRead() )来暂停调用线程,直到操作完成。
本例演示的是同步方法。终端示例说明了异步方法。
本例的目的是演示一种模式,您可以用它来简化串行编程代码,同时又不会降低用户界面的响应速度。使用 Qt 的阻塞串行编程 API 通常可以简化代码,但由于其阻塞行为,因此只能在非用户界面线程中使用,以防止用户界面冻结。但与许多人的想法相反,在QThread 中使用线程并不一定会给应用程序增加难以管理的复杂性。
此应用程序是一个接收器,用于演示与发送器应用程序搭配使用的阻塞发送器示例。
接收器应用程序通过串行端口接收发送器应用程序的请求,并向其发送响应。
我们将从处理串行编程代码的 ReceiverThread 类开始。
class ReceiverThread : public QThread { Q_OBJECT public: explicit ReceiverThread(QObject *parent = nullptr); ~ReceiverThread(); void startReceiver(const QString &portName, int waitTimeout, const QString &response); signals: void request(const QString &s); void error(const QString &s); void timeout(const QString &s); private: void run() override; QString m_portName; QString m_response; int m_waitTimeout = 0; QMutex m_mutex; bool m_quit = false; };
ReceiverThread 是一个QThread 子类,它为接收来自 Sender 的请求提供了一个 API,并为发送响应和报告错误提供了信号。
您应该调用 startReceiver() 来启动 Receiver 应用程序。该方法将配置和启动串行接口所需的参数传递给 ReceiverThread。当 ReceiverThread 收到发送方的任何请求时,就会发出 request() 信号。如果发生错误,则发出 error() 或 timeout() 信号。
值得注意的是,startReceiver() 由 GUI 主线程调用,但响应数据和其他参数将由 ReceiverThread 的线程访问。ReceiverThread 的数据成员会同时从不同线程读取和写入,因此建议使用QMutex 来同步访问。
void ReceiverThread::startReceiver(const QString &portName, int waitTimeout, const QString &response) { const QMutexLocker locker(&m_mutex); m_portName = portName; m_waitTimeout = waitTimeout; m_response = response; if (!isRunning()) start(); }
startReceiver() 函数存储串行端口名称、超时和响应数据,QMutexLocker 锁定互斥以保护这些数据。然后我们启动线程,除非它已经在运行。QWaitCondition::wakeOne稍后将讨论 run() 函数。
void ReceiverThread::run() { bool currentPortNameChanged = false; m_mutex.lock(); QString currentPortName; if (currentPortName != m_portName) { currentPortName = m_portName; currentPortNameChanged = true; } int currentWaitTimeout = m_waitTimeout; QString currentRespone = m_response; m_mutex.unlock();
在 run() 函数中,首先获取互斥锁,从成员数据中获取串行端口名称、超时和响应数据,然后再次释放锁。在任何情况下,都不应在获取这些数据的进程中同时调用startReceiver()
方法。QString 是可重入的,但不是线程安全的,不建议从一个启动、调用和超时或响应数据的进程中读取串行端口名称。ReceiverThread 一次只能处理一个启动。
在进入循环之前,我们会在堆栈中的 run() 函数中构建QSerialPort 对象:
QSerialPort serial; while (!m_quit) {
这允许我们在运行循环时创建一个对象,同时也意味着该对象的所有方法都将在 run() 线程的上下文中执行。
在循环中,检查当前启动的串行端口名称是否已更改。如果已更改,则重新打开并重新配置串行端口。
if (currentPortName.isEmpty()) { emit error(tr("Port not set")); return; } else 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; } } if (serial.waitForReadyRead(currentWaitTimeout)) {
循环将继续等待请求数据:
// read request QByteArray requestData = serial.readAll(); while (serial.waitForReadyRead(10)) requestData += serial.readAll();
警告: 对于阻塞方法,应在每次读取()调用前使用 waitForReadyRead()方法,因为它会处理所有 I/O 例程,而不是 Qt 事件循环。
如果读取数据时发生错误,就会发出 timeout() 信号。
} else { emit timeout(tr("Wait read request timeout %1") .arg(QTime::currentTime().toString())); }
读取成功后,尝试发送响应并等待传输完成:
// write response const QByteArray responseData = currentRespone.toUtf8(); serial.write(responseData); if (serial.waitForBytesWritten(m_waitTimeout)) { const QString request = QString::fromUtf8(requestData); emit this->request(request);
警告: 对于阻塞方法,应在每次写入()调用后使用 waitForBytesWritten() 方法,因为该方法处理所有 I/O 例程,而不是 Qt 事件循环。
如果在写入数据时发生错误,就会发出 timeout() 信号。
} else { emit timeout(tr("Wait write response timeout %1") .arg(QTime::currentTime().toString())); }
写入成功后,会发出 request() 信号,其中包含从发送方应用程序接收到的数据:
emit this->request(request);
接下来,线程会切换到读取串行接口的当前参数(因为这些参数可能已经更新),并从头开始运行循环。
m_mutex.lock(); if (currentPortName != m_portName) { currentPortName = m_portName; currentPortNameChanged = true; } else { currentPortNameChanged = false; } currentWaitTimeout = m_waitTimeout; currentRespone = m_response; m_mutex.unlock(); }
运行示例
运行示例 Qt Creator,打开Welcome 模式,并从Examples 中选择示例。更多信息,请参阅Qt Creator: Tutorial:构建并运行。
© 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.