Blockierender Empfänger
Zeigt, wie man die synchrone API von QSerialPort in einem Nicht-GUI-Thread verwendet.
Blocking Receiver zeigt, wie man eine Anwendung für eine serielle Schnittstelle unter Verwendung der synchronen API von QSerialPort in einem Nicht-GUI-Thread erstellt.
QSerialPort unterstützt zwei allgemeine Programmieransätze:
- Der asynchrone (nicht-blockierende) Ansatz. Operationen werden geplant und ausgeführt, wenn die Steuerung zur Qt-Ereignisschleife zurückkehrt. QSerialPort gibt ein Signal aus, wenn die Operation beendet ist. Zum Beispiel kehrt QSerialPort::write() sofort zurück. Wenn die Daten an die serielle Schnittstelle gesendet werden, gibt QSerialPort das Signal bytesWritten() aus.
- Der synchrone (blockierende) Ansatz. In Nicht-GUI- und Multithread-Anwendungen können die
waitFor...()
-Funktionen aufgerufen werden (d. h. QSerialPort::waitForReadyRead()), um den aufrufenden Thread anzuhalten, bis die Operation 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 demonstrieren, das Sie verwenden können, um Ihren seriellen Programmiercode zu vereinfachen, ohne die Reaktionsfähigkeit Ihrer Benutzeroberfläche zu verlieren. Die Verwendung der blockierenden seriellen Programmier-API von Qt führt oft zu einfacherem Code, sollte aber wegen ihres blockierenden Verhaltens nur in Nicht-GUI-Threads verwendet werden, um ein Einfrieren der Benutzeroberfläche zu verhindern. Im Gegensatz zu dem, was viele denken, führt die Verwendung von Threads mit QThread nicht unbedingt zu einer unüberschaubaren Komplexität Ihrer Anwendung.
Bei dieser Anwendung handelt es sich um einen Receiver, der die Arbeit zusammen mit der Senderanwendung Blocking Sender Beispiel demonstriert.
Die Receiver-Anwendung empfängt die Anfrage über die serielle Schnittstelle von der Sender-Anwendung und sendet eine Antwort an diese.
Wir beginnen mit der Klasse ReceiverThread, die den seriellen Programmiercode verarbeitet.
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 für den Empfang von Anfragen vom Sender bereitstellt und über Signale für die Übermittlung von Antworten und die Meldung von Fehlern verfügt.
Sie sollten startReceiver() aufrufen, um die Receiver-Anwendung zu starten. Diese Methode übergibt dem ReceiverThread die gewünschten Parameter zum Konfigurieren und Starten der seriellen Schnittstelle. Wenn der ReceiverThread vom Sender eine Anfrage erhält, sendet er das request()-Signal. Wenn ein Fehler auftritt, werden die Signale error() oder timeout() ausgegeben.
Es ist wichtig zu beachten, dass startReceiver() vom GUI-Hauptthread aufgerufen wird, aber auf die Antwortdaten und andere Parameter wird vom ReceiverThread-Thread zugegriffen. Die Datenelemente von ReceiverThread werden von verschiedenen Threads gleichzeitig gelesen und geschrieben, daher ist es ratsam, QMutex zu verwenden, um den Zugriff zu synchronisieren.
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, die Zeitüberschreitung 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. QWaitCondition::wakeOne() wird später besprochen.
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 run()-Funktion wird zunächst die Mutex-Sperre übernommen, der Name der seriellen Schnittstelle, die Zeitüberschreitung und die Antwortdaten aus den Mitgliedsdaten geholt und die Sperre dann wieder aufgehoben. Unter keinen Umständen sollte die Methode startReceiver()
gleichzeitig mit einem Prozess aufgerufen werden, der diese Daten abruft. QString ist reentrant, aber nicht thread-sicher, und es wird nicht empfohlen, den Namen der seriellen Schnittstelle von einem Startup, Aufruf und Timeout oder Antwortdaten von einem anderen zu lesen. ReceiverThread kann jeweils nur einen Startvorgang verarbeiten.
Das Objekt QSerialPort wird auf dem Stack in der Funktion run() erstellt, bevor die Schleife beginnt:
QSerialPort serial; while (!m_quit) {
Dies ermöglicht es uns, einmal ein Objekt zu erstellen, während die Schleife läuft, und bedeutet auch, dass alle Methoden des Objekts im Kontext des run()-Threads ausgeführt werden.
Prüfen Sie in der Schleife, ob sich der Name der seriellen Schnittstelle für den aktuellen Start geändert hat oder nicht. 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 vor jedem read()-Aufruf für den blockierenden Ansatz verwendet werden, da sie alle E/A-Routinen anstelle der Qt-Event-Schleife verarbeitet.
Das timeout()-Signal wird ausgegeben, wenn beim Lesen der Daten ein Fehler auftritt.
} else { emit timeout(tr("Wait read request timeout %1") .arg(QTime::currentTime().toString())); }
Nach einem erfolgreichen Lesevorgang sollte man versuchen, eine Antwort zu senden und auf den Abschluss der Übertragung warten:
// 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: Die Methode waitForBytesWritten() sollte nach jedem write()-Aufruf für den blockierenden Ansatz verwendet werden, da sie alle I/O-Routinen anstelle der Qt-Event-Schleife verarbeitet.
Das timeout()-Signal wird ausgegeben, wenn beim Schreiben von Daten ein Fehler auftritt.
} else { emit timeout(tr("Wait write response timeout %1") .arg(QTime::currentTime().toString())); }
Nach erfolgreichem Schreiben wird das request()-Signal ausgegeben, das die von der Sender-Anwendung empfangenen Daten enthält:
emit this->request(request);
Danach schaltet der Thread auf das Lesen der aktuellen Parameter für die serielle Schnittstelle um, da diese bereits aktualisiert worden sein können, und führt die Schleife von Anfang an 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
Zum Ausführen des Beispiels von Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.
Siehe auch Serielles Terminal und Blocking Sender.
© 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.