Auf dieser Seite

Absender blockieren

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

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

Qt Serial Port blockiert den Sender

QSerialPort unterstützt zwei Programmieralternativen:

  • Die asynchrone (nicht blockierende) Alternative. Operationen werden eingeplant und ausgeführt, sobald die Kontrolle an die Qt-Ereignisschleife zurückgegeben wird. Die Klasse „ QSerialPort “ sendet ein Signal aus, sobald die Operation abgeschlossen ist. Beispielsweise kehrt die Methode „ write()“ sofort zurück. Wenn die Daten an den seriellen Port gesendet werden, sendet die Klasse „ QSerialPort “ das Signal „ bytesWritten()“ aus.
  • Die synchrone (blockierende) Alternative. In headless- und multithreaded-Anwendungen kann die Methode `wait` (in diesem Fall ` waitForReadyRead()`) aufgerufen werden, um den aufrufenden Thread so lange anzuhalten, bis der Vorgang abgeschlossen ist.

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

Der Zweck dieses Beispiels besteht darin, zu veranschaulichen, wie Sie Ihren seriellen Programmcode vereinfachen können, ohne die Reaktionsfähigkeit der Benutzeroberfläche zu beeinträchtigen. Die blockierende API für die serielle Programmierung führt oft zu einfacherem Code, sollte jedoch nur in Nicht-GUI-Threads verwendet werden, um die Reaktionsfähigkeit der Benutzeroberfläche zu gewährleisten.

Diese Anwendung ist der Sender, der die Zusammenarbeit mit der Empfängeranwendung „Blocking Receiver“ veranschaulicht.

Die Sender-Anwendung initiiert die Übertragungsanforderung über die serielle Schnittstelle an die Empfänger-Anwendung und wartet auf eine 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 zur Planung von Anfragen an den Empfänger bereitstellt. Diese Klasse stellt Signale für die Antwort und die Meldung von Fehlern bereit. Die Methode „transaction()“ kann aufgerufen werden, um die neue Sender-Transaktion mit der gewünschten Anfrage zu starten. Das Ergebnis wird über das Signal „response()“ bereitgestellt. Im Falle von Problemen wird das Signal „error()“ oder „timeout()“ ausgelöst.

Beachten Sie, dass die Methode `transaction()` im Haupt-Thread aufgerufen wird, die Anfrage jedoch im `SenderThread`-Thread bereitgestellt wird. Die Datenelemente des `SenderThread` werden parallel in verschiedenen Threads 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, das Timeout und die Anforderungsdaten. 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 erläutert.

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 Funktion `run()` wird zunächst das ` QMutex `-Objekt gesperrt, anschließend werden der Name der seriellen Schnittstelle, das Timeout und die Anforderungsdaten mithilfe der Member-Daten abgerufen. Danach wird die Sperre von ` QMutex ` freigegeben.

Unter keinen Umständen darf die Methode ` transaction() ` gleichzeitig mit einem Prozess aufgerufen werden, der Daten abruft. Beachten Sie, dass die Klasse ` QString ` zwar reentrant, aber nicht threadsicher ist. Daher wird davon abgeraten, den Namen der seriellen Schnittstelle in einem Anforderungsthread zu lesen und die Zeitüberschreitung oder die Anforderungsdaten in einem anderen Thread abzurufen. Die Klasse „SenderThread“ kann jeweils nur eine Anfrage bearbeiten.

Das „ QSerialPort “-Objekt wird in der „run()“-Methode vor dem Eintritt in die Schleife auf dem Stack erstellt:

    QSerialPort serial;

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

    while (!m_quit) {

Dadurch ist es möglich, ein Objekt während der Ausführung der Schleife zu erstellen. Das bedeutet auch, dass alle Methoden des Objekts im Geltungsbereich der Methode `run()` ausgeführt werden.

Innerhalb der Schleife wird geprüft, ob sich der Name der seriellen Schnittstelle der aktuellen Transaktion geändert hat. Ist dies der Fall, wird die serielle Schnittstelle erneut geöffnet und anschließend 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 fordert weiterhin Daten an, schreibt an den seriellen Port und wartet, bis alle Daten übertragen sind.

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

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

Das Signal `timeout()` wird ausgelöst, wenn bei der Datenübertragung ein Timeout-Fehler auftritt.

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

Nach einer erfolgreichen Abfrage folgt eine Wartezeit auf eine Antwort, anschließend 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);

Warnung: Wie bei der blockierenden Variante sollte die Methode ` waitForReadyRead()` vor jedem Aufruf von `read()` verwendet werden. Dadurch werden alle E/A-Routinen außerhalb der Qt-Ereignisschleife verarbeitet.

Das Signal „timeout()“ wird ausgelöst, 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 Signal `response()` die von der Empfängeranwendung empfangenen Daten:

                emit this->response(response);

Anschließend geht der Thread in den Ruhezustand, bis die nächste Transaktion eintrifft. Nach dem Aufwachen liest der Thread die neuen Daten mithilfe der Member und führt die Schleife von Anfang an erneut aus.

        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

Sie können das Beispiel wie folgt ausführen:

Beispielprojekt @ code.qt.io

Siehe auch „Serielle Schnittstelle “ und „Blockierender Empfänger“.

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