Auf dieser Seite

Blockierender Empfänger

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

„Blockierender Empfänger“ zeigt, wie man eine Anwendung für eine serielle Schnittstelle unter Verwendung der synchronen API von „ QSerialPort “ in einem Nicht-GUI-Thread erstellt.

Qt Serial Port für serielle Schnittstellen (Blockierung)

QSerialPort unterstützt zwei allgemeine Programmieransätze:

  • Den asynchronen (nicht blockierenden) Ansatz. Operationen werden eingeplant und ausgeführt, sobald die Kontrolle an die Ereignisschleife von Qt zurückgegeben wird. „ QSerialPort “ sendet ein Signal, sobald die Operation abgeschlossen ist. Beispielsweise kehrt „ QSerialPort::write()“ sofort zurück. Wenn die Daten an den seriellen Port gesendet werden, sendet „ QSerialPort “ das Signal „ bytesWritten()“.
  • Der synchrone (blockierende) Ansatz. In Nicht-GUI- und Multithread-Anwendungen können die waitFor...() -Funktionen aufgerufen werden (z. B. QSerialPort::waitForReadyRead()), um den aufrufenden Thread so lange anzuhalten, bis der Vorgang abgeschlossen ist.

In diesem Beispiel wird der synchrone Ansatz demonstriert. Das Terminal- Beispiel veranschaulicht den asynchronen Ansatz.

Der Zweck dieses Beispiels ist es, ein Muster zu veranschaulichen, mit dem Sie Ihren Code für die serielle Programmierung vereinfachen können, ohne dass die Reaktionsfähigkeit Ihrer Benutzeroberfläche beeinträchtigt wird. Die Verwendung der blockierenden seriellen Programmier-API von Qt führt oft zu einfacherem Code, sollte jedoch aufgrund ihres blockierenden Verhaltens nur in Nicht-GUI-Threads verwendet werden, um ein Einfrieren der Benutzeroberfläche zu verhindern. Entgegen der weit verbreiteten Meinung führt die Verwendung von Threads mit „ QThread “ jedoch nicht zwangsläufig zu einer unüberschaubaren Komplexität Ihrer Anwendung.

Diese Anwendung ist ein Empfänger, der die Zusammenarbeit mit der Sender-Anwendung aus dem Beispiel „Blocking Sender“ demonstriert.

Die „Receiver“-Anwendung empfängt die Anfrage über die serielle Schnittstelle von der „Sender“-Anwendung und sendet eine Antwort zurück.

Wir beginnen mit der Klasse „ReceiverThread“, die den Code für die serielle Programmierung verwaltet.

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“ ist eine Unterklasse von „ QThread “, die eine API zum Empfangen von Anfragen vom Sender bereitstellt und über Signale zum Senden von Antworten sowie zum Melden von Fehlern verfügt.

Sie sollten `startReceiver()` aufrufen, um die Empfängeranwendung zu starten. Diese Methode übergibt an `ReceiverThread` die erforderlichen Parameter zur Konfiguration und zum Start der seriellen Schnittstelle. Wenn `ReceiverThread` eine Anfrage vom Sender empfängt, gibt er das Signal `request()` aus. Tritt ein Fehler auf, werden die Signale `error()` oder `timeout()` ausgegeben.

Es ist wichtig zu beachten, dass `startReceiver()` vom Haupt-GUI-Thread aus aufgerufen wird, der Zugriff auf die Antwortdaten und andere Parameter jedoch über den `ReceiverThread` erfolgt. Da die Datenelemente des `ReceiverThread` von verschiedenen Threads gleichzeitig gelesen und geschrieben werden, empfiehlt es sich, ` QMutex ` zur Synchronisierung des Zugriffs zu verwenden.

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

Die Funktion `startReceiver()` speichert den Namen der seriellen Schnittstelle, das Timeout und die Antwortdaten, und ` QMutexLocker ` sperrt den Mutex, um diese Daten zu schützen. Anschließend starten wir den Thread, sofern er nicht bereits läuft. Auf ` QWaitCondition::wakeOne()` wird später eingegangen.

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

In der Funktion `run()` wird zunächst die Mutex-Sperre erworben, der Name der seriellen Schnittstelle, das Timeout und die Antwortdaten aus den Member-Daten abgerufen und anschließend die Sperre wieder freigegeben. Unter keinen Umständen darf die Methode ` startReceiver() ` gleichzeitig mit einem Prozess aufgerufen werden, der diese Daten abruft. ` QString ` ist reentrant, aber nicht threadsicher, und es wird nicht empfohlen, den Namen der seriellen Schnittstelle aus einem Startvorgang und die Aufruf-, Timeout- oder Antwortdaten aus einem anderen zu lesen. `ReceiverThread` kann jeweils nur einen Startvorgang verarbeiten.

Das „ QSerialPort “-Objekt, das wir auf dem Stack in der Funktion „run()“ vor dem Eintritt in die Schleife erstellen:

    QSerialPort serial;

    while (!m_quit) {

Dadurch können wir während der Ausführung der Schleife ein einziges Mal ein Objekt erstellen, was auch bedeutet, dass alle Methoden des Objekts im Kontext des run()-Threads ausgeführt werden.

Prüfen Sie innerhalb der Schleife, ob sich der Name der seriellen Schnittstelle für den aktuellen Startvorgang geändert hat. Ist dies der Fall, öffnen Sie die serielle Schnittstelle erneut und konfigurieren Sie sie neu.

        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)) {

Die Schleife wartet weiterhin auf Anforderungsdaten:

            // read request
            QByteArray requestData = serial.readAll();
            while (serial.waitForReadyRead(10))
                requestData += serial.readAll();

Warnung: Die Methode `waitForReadyRead()` sollte bei der blockierenden Vorgehensweise vor jedem Aufruf von `read()` verwendet werden, da sie alle E/A-Routinen anstelle der Qt-Ereignisschleife verarbeitet.

Das Signal `timeout()` wird ausgelöst, wenn beim Lesen von Daten ein Fehler auftritt.

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

Versuchen Sie nach einem erfolgreichen Lesevorgang, eine Antwort zu senden, und warten Sie auf den Abschluss der Übertragung:

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

Warnung: Bei der blockierenden Vorgehensweise solltedie Methode `waitForBytesWritten()` nach jedem `write()`-Aufruf verwendet werden, da sie alle E/A-Routinen anstelle der Qt-Ereignisschleife verarbeitet.

Das Signal „timeout()“ wird ausgelöst, wenn beim Schreiben von Daten ein Fehler auftritt.

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

Nach einem erfolgreichen Schreibvorgang wird das Signal `request()` ausgelöst, das die von der Sender-Anwendung empfangenen Daten enthält:

                emit this->request(request);

Anschließend wechselt der Thread zum Auslesen der aktuellen Parameter für die serielle Schnittstelle, da diese möglicherweise bereits aktualisiert wurden, und führt die Schleife von Anfang an erneut aus.

        m_mutex.lock();
        if (currentPortName != m_portName) {
            currentPortName = m_portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = m_waitTimeout;
        currentRespone = m_response;
        m_mutex.unlock();
    }

Ausführen des Beispiels

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

Beispielprojekt @ code.qt.io

Siehe auch „Serial Terminal “ und „Blocking Sender“.

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