发送器阻止

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

发送器阻止演示如何在工作线程中使用QSerialPort的同步API创建串行接口应用程序。

QSerialPort支持两种编程方案

  • 异步(非阻塞)方案。操作在控制权返回到Qt事件循环时被安排和执行。当操作完成时,QSerialPort类会发射一个信号。例如,write()方法立即返回。当数据发送到串行端口时,QSerialPort类会发射bytesWritten()信号。
  • 同步(阻塞)方案。在无头和多线程应用程序中,可以调用等待方法(在这种情况下,waitForReadyRead)来挂起调用线程,直到操作完成。

在此示例中,演示了同步方案。终端示例说明了异步方案。

本示例的目的是演示如何简化您的串行编程代码,同时不丢失用户界面的响应性。阻塞串行编程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)) {

警告:对于阻塞传输,应在每次调用 waitForBytesWritten() 方法和每次 write 方法后使用。这将处理所有 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 运行示例,请打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

示例项目 @ code.qt.io

另请参阅串行终端阻塞接收器

© 2024 The Qt Company Ltd. 包含在此的文档贡献来自各自的所有权。提供的文档根据 Free Software Foundation 发布的《GNU 自由文档许可证》版本 1.3 的条款获得许可。Qt 及其相关标志是芬兰和/或其他国家的 The Qt Company Ltd. 的商标。所有其他商标均为其各自所有者的财产。