Blockierender Sender

Zeigt, wie man die synchrone API von QSerialPort in einem Worker-Thread verwendet.

Blocking Sender zeigt, wie man eine Anwendung für eine serielle Schnittstelle unter Verwendung der synchronen API von QSerialPort in einem Worker-Thread erstellt.

QSerialPort unterstützt zwei Programmieralternativen:

  • Die asynchrone (nicht-blockierende) Alternative. Die Operationen werden geplant und ausgeführt, wenn die Kontrolle zur Qt-Ereignisschleife zurückkehrt. Die Klasse QSerialPort gibt ein Signal aus, wenn die Operation beendet ist. Zum Beispiel kehrt die Methode write() sofort zurück. Wenn die Daten an die serielle Schnittstelle gesendet werden, gibt die Klasse QSerialPort das Signal bytesWritten() aus.
  • Die synchrone (blockierende) Alternative. In Headless- und Multithread-Anwendungen kann die Wait-Methode aufgerufen werden (in diesem Fall waitForReadyRead()), um den aufrufenden Thread anzuhalten, bis der Vorgang abgeschlossen ist.

In diesem Beispiel wird die synchrone Alternative demonstriert. Das Terminal-Beispiel veranschaulicht die asynchrone Alternative.

Mit diesem Beispiel soll gezeigt werden, wie Sie Ihren seriellen Programmiercode vereinfachen können, ohne die Reaktionsfähigkeit der Benutzeroberfläche zu beeinträchtigen. Die blockierende serielle Programmier-API führt oft zu einfacherem Code, sollte aber nur in Nicht-GUI-Threads verwendet werden, damit die Benutzeroberfläche reaktionsschnell bleibt.

Diese Anwendung ist der Sender, der die Arbeit zusammen mit der Empfängeranwendung demonstriert Blocking Receiver example.

Die Senderanwendung leitet die Übertragungsanforderung über die serielle Schnittstelle an die Empfängeranwendung ein und wartet auf die Antwort.

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 ist eine Unterklasse von QThread, die eine API für die Planung von Anforderungen an den Empfänger bereitstellt. Diese Klasse bietet Signale für die Beantwortung und die Meldung von Fehlern. Die Methode transaction() kann aufgerufen werden, um die neue Sendertransaktion mit der gewünschten Anfrage zu starten. Das Ergebnis wird durch das Signal response() bereitgestellt. Im Falle von Problemen wird das error()- oder timeout()-Signal ausgegeben.

Beachten Sie, dass die transaction()-Methode im Hauptthread aufgerufen wird, die Anfrage jedoch im SenderThread-Thread bereitgestellt wird. Die Datenelemente des SenderThread werden in verschiedenen Threads gleichzeitig gelesen und geschrieben, daher wird die Klasse QMutex zur Synchronisierung des Zugriffs verwendet.

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();
}

Die Methode transaction() speichert den Namen der seriellen Schnittstelle, den Timeout und die Anfragedaten. Der Mutex kann mit QMutexLocker gesperrt werden, um diese Daten zu schützen. Der Thread kann dann gestartet werden, sofern er nicht bereits läuft. Die Methode wakeOne() wird später besprochen.

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();

In der run()-Funktion wird zunächst das QMutex -Objekt gesperrt, dann werden der Name der seriellen Schnittstelle, die Zeitüberschreitung und die Anforderungsdaten mit Hilfe der Mitgliedsdaten abgefragt. Danach wird die Sperre von QMutex wieder aufgehoben.

Unter keinen Umständen sollte die Methode transaction() gleichzeitig mit einem Prozess aufgerufen werden, der die Daten abruft. Beachten Sie, dass die Klasse QString zwar ablaufinvariant, aber nicht thread-sicher ist. Daher ist es nicht empfehlenswert, den Namen der seriellen Schnittstelle in einem Anfrage-Thread zu lesen und in einem anderen Thread eine Zeitüberschreitung zu verursachen oder Daten anzufordern. Die Klasse SenderThread kann jeweils nur eine Anfrage bearbeiten.

Das Objekt QSerialPort wird in der run()-Methode auf dem Stack erstellt, bevor es in die Schleife gelangt:

    QSerialPort serial;

    if (currentPortName.isEmpty()) {
        emit error(tr("No port name specified"));
        return;
    }

    while (!m_quit) {

Dadurch ist es möglich, ein Objekt zu erstellen, während die Schleife läuft. Es bedeutet auch, dass alle Objektmethoden im Bereich der run()-Methode ausgeführt werden.

Innerhalb der Schleife wird geprüft, ob sich der Name der seriellen Schnittstelle der aktuellen Transaktion geändert hat oder nicht. Hat sich dieser geändert, wird die serielle Schnittstelle erneut geöffnet und dann neu konfiguriert.

        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;
            }
        }

Die Schleife fährt fort, Daten anzufordern, an die serielle Schnittstelle zu schreiben und zu warten, bis alle Daten übertragen sind.

        // write request
        const QByteArray requestData = currentRequest.toUtf8();
        serial.write(requestData);
        if (serial.waitForBytesWritten(m_waitTimeout)) {

Achtung! Wie bei der blockierenden Übertragung sollte die Methode waitForBytesWritten() nach jedem Aufruf der Methode write verwendet werden. Dadurch werden alle E/A-Routinen anstelle der Qt-Ereignisschleife abgearbeitet.

Das timeout()-Signal wird ausgegeben, wenn ein Timeout-Fehler bei der Übertragung von Daten auftritt.

        } else {
            emit timeout(tr("Wait write request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }

Nach einer erfolgreichen Anfrage wird eine Zeit lang auf die Antwort gewartet, dann wird erneut gelesen.

            // 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);

Achtung! Wie bei der blockierenden Alternative sollte die Methode waitForReadyRead() vor jedem read()-Aufruf verwendet werden. Dadurch werden alle E/A-Routinen anstelle der Qt-Ereignisschleife verarbeitet.

Das timeout()-Signal wird ausgegeben, wenn beim Empfang von Daten ein Timeout-Fehler auftritt.

            } else {
                emit timeout(tr("Wait read response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }

Wenn eine Transaktion erfolgreich abgeschlossen wurde, enthält das response()-Signal die von der Empfängeranwendung empfangenen Daten:

                emit this->response(response);

Danach geht der Thread in den Schlaf, bis die nächste Transaktion erscheint. Der Thread liest die neuen Daten nach dem Aufwachen mit Hilfe der Mitglieder und führt die Schleife von Anfang an durch.

        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();
    }

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Beispielprojekt @ code.qt.io

Siehe auch Serielles Terminal und Blockieren des Empfängers.

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