本页内容

阻塞发送方

演示如何在工作线程中使用QSerialPort 的同步 API。

“阻塞发送器”演示了如何在工作线程中使用QSerialPort 的同步API来创建一个串行接口应用程序。

Qt Serial Port 阻塞式发送器

QSerialPort 支持两种编程方式:

  • 异步(非阻塞)方式。操作会被排入队列,并在控制权返回 Qt 事件循环时执行。当操作完成时,QSerialPort 类会发出一个信号。例如,write() 方法会立即返回。当数据发送至串行端口时,QSerialPort 类会发出bytesWritten() 信号。
  • 同步(阻塞)方式。在无界面和多线程应用程序中,可以调用 wait 方法(在此示例中为waitForReadyRead())来挂起调用线程,直到操作完成。

本示例演示了同步方案。Terminal示例则演示了异步方案。

本示例旨在演示如何在不影响用户界面响应性的前提下简化串行编程代码。阻塞式串行编程 API 通常能简化代码,但应仅在非 GUI 线程中使用,以确保用户界面保持响应。

本应用程序作为发送方,与接收方应用程序“阻塞式接收器”示例配合使用。

发送方应用程序通过串口向接收方应用程序发起传输请求,并等待响应。

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()` 信号。

请注意,transaction() 方法在主线程中调用,但请求是在 SenderThread 线程中提交的。由于 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();
    }

运行示例

您可以通过以下方式运行示例:

示例项目 @ code.qt.io

另请参阅 “串行终端”和“阻塞式接收器”。

© 2026 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.