Qt Thread
-
Hallo,
ich habe mir jetzt mal ein bisschen was zu Threads durchgelesen. Um ein Wenig Praxis zu bekommen möchte ich das Ganze in einen Thread auslagern:#ifndef PORTLISTENER_H_ #define PORTLISTENER_H_ #include <QObject> #include "qextserialport.h" class PortListener : public QObject { Q_OBJECT public: PortListener(const QString & portName); private: QextSerialPort *port; private slots: void onReadyRead(); void onDsrChanged(bool status); }; #endif /*PORTLISTENER_H_*/
#include "PortListener.h" #include <QtDebug> PortListener::PortListener(const QString & portName) { qDebug() << "hi there"; this->port = new QextSerialPort(portName, QextSerialPort::EventDriven); port->setBaudRate(BAUD9600); port->setFlowControl(FLOW_OFF); port->setParity(PAR_NONE); port->setDataBits(DATA_8); port->setStopBits(STOP_1); if (port->open(QIODevice::ReadWrite) == true) { connect(port, SIGNAL(readyRead()), this, SLOT(onReadyRead())); connect(port, SIGNAL(dsrChanged(bool)), this, SLOT(onDsrChanged(bool))); if (!(port->lineStatus() & LS_DSR)) qDebug() << "warning: device is not turned on"; qDebug() << "listening for data on" << port->portName(); } else { qDebug() << "device failed to open:" << port->errorString(); } } void PortListener::onReadyRead() { QByteArray bytes; int a = port->bytesAvailable(); bytes.resize(a); port->read(bytes.data(), bytes.size()); qDebug() << "bytes read:" << bytes.size(); qDebug() << "bytes:" << bytes; } void PortListener::onDsrChanged(bool status) { if (status) qDebug() << "device was turned on"; else qDebug() << "device was turned off"; }
Das ganze ist eine Klasse, die Daten auf der Seriellen Schnittstelle empfängt. Das es eigentlich nicht nötig ist, hier mit Threads zu arbeiten sei mal dahingestellt. Mir geht es nur um die Übung.
Was ich möchte. In einem 2. Thread soll das Protokoll ausgelagert werden. Also z.B. muss das erste Byte den Start der Übertragung signalisieren, das 2. gibt an, wie viele Bytes folgen, das letzte nach diesen Bytes ist das Stopp Byte.
Szenario1:
Wurden die Daten empfangen gehe ich mal davon aus, das damit komplizierte Berechnungen gemacht werden. Das soll hier mal ausgelassenen werden. Diese Daten werden nach der Berechnung ausgewertet und nach verschiedenen geprüften Bedienungen wird ggf. etwas in der GUI ausgegeben.Szenario2:
Es sollen Daten empfangen werden, dann in einer queue in den Hauptthread übergeben werden.Mir scheitert es jetzt noch an der Idee zur Umsetzung.
1. Wie wird der Thread realisiert. Er soll ja immer laufen. Aber wenn ich eine While(1) schleife in die run Methode mache heitzt ja die ganze Zeit der CPU.
2. Die Kommunikation mit dem Hauptthread
In Szenario1. Wie greife ich da auf die GUI Elemente zu. Ich denke mal das muss über Signals und SLots gehen. Also z.B. mache ich ein Signal CalculationDone im Thread und im Hauptthread einen Slot onCalculationDone. Aber kann ich einfach so mit Signals und Slots Thrads verbinden? Und ist das Ganze dann Threadsicher?
In Szenario 2. Stellt Qt bereits eine Threadsichere Queue bereit. Also das ich nur im Thread sagen muss hinten anhängen, und im Mainthrad Daten abrufen. So, dass ich mich nciht um manuelles mutexen kümmern muss? Und wie rufe ich die Daten dann in Main ab? Ih komme ja gar nicht in die Hauptschleife. Das Programm ist ja in der Eventloop. Also muss das ganze dann auch zwangsweise mit Signals und Slots realisiert werden, oder?
Es wäre nett, wenn mich jemand bei meiner Übung unterstützen würde.
Viele Grüße
Jan++
-
Du brauchst keine Endlosschleife. Du hast SIGNALS/SLOTS. In deiner Klasse mach folgendes:
class PortListener : public QThread { Q_OBJECT void run() { // Objekte Instantiieren und initialisieren. // sonstige Konfiguration deines Setup. // connects erstellen exec(); // startet in dem Thread eine eigene Eventloop // somit werden SLOTS im Context des neuen Threads ausgeführt // Der Mainthread und damit die GUI laufen separat. } };
Du kannst jetzt jederzeit SIGNALS emittieren. Diese können auch aus dem MainThread gefangen werden.
So kannst du dann sehr einfach auf neue Ergebnisse von Berechnungen reagieren.Auf die GUI-Elemente kannst du nur aus dem MainThread zugreifen. Wenn du also Elemente aktualisieren willst, mach dies in einem SLOT im MainThread, der an einem (beliebigen) SIGNAL hängt.
-
Danke das werde ich probieren. Also haut das mit der Calculation done schon hin. Was gebe ich da in der connect Methode als ziel ein? Da brauche ich ja einen Zeiger auf den Hauptthread. Und ist das Ganze dann Thread sicher, oder muss ich noch was mutexen?
Und dann würde mich eben noch das interessieren.
In Szenario 2: Stellt Qt bereits eine Threadsichere Queue bereit? Also das ich nur im Thread sagen muss hinten anhängen, und im Mainthrad Daten abrufen. So, dass ich mich nciht um manuelles mutexen kümmern muss? Und wie rufe ich die Daten dann in Main ab? Ih komme ja gar nicht in die Hauptschleife. Das Programm ist ja in der Eventloop. Also muss das ganze dann auch zwangsweise mit Signals und Slots realisiert werden, oder?
-
Gut ich habe mir noch mal ein paar Gedanken zur Vernetzung gemacht. Ich muss das connect im Hauptthread machen und von da den Thread starten.
Ich habe mir folgende Struktur überlegt:
Ich lege eine Klasse an SerialThread in der wird der port Listener gestartet.
main...MainWindow wird gestartet.
im MainWindow Konstruktor wird der Thread gestartet und das Connect gemacht. Nur mein Problem ist jetzt ich möchte ja ein Signal senden, was vom Hauptthread abgefangen wird. Sollte ich das besser in SerialThread emmitieren, oder im PortListener?
-
Jan++ schrieb:
im MainWindow Konstruktor wird der Thread gestartet und das Connect gemacht. Nur mein Problem ist jetzt ich möchte ja ein Signal senden, was vom Hauptthread abgefangen wird. Sollte ich das besser in SerialThread emmitieren, oder im PortListener?
"Der Thread" wird wohl der SerialThread sein.
Du kannst nur Objekte connecten, auf die du Zugriff hast. Entweder gestattet SerialThread Zugriff auf sein PortListener-Member, dann kannst du direkt MainWindow<->PortListener connecten.
Ansonsten kannst du auch SIGNAL auf SIGNAL connecten:class SerialThread : public QThread { Q_OBJECT PortListener* listener; SerialThread() : listener(new PortListener(this)) { // schickt das newListenerData()-SIGNAL als newData()-SIGNAL weiter. connect(listener, SIGNAL(newListenerData()), SIGNAL(newData())); } signals: void newData(); };
so ungefähr...
Dann connected MainWIndow auf SerialThread::newData().
-
Ah Danke. Also das Signal Signal connect erscheint mir am plausibelsten. Muss ich eigentlich die dynamich angelegten Objecte SerialThread und Portlistener wieder löschen mit delete?
Und ist das Connect nun Thread sicher= Und wie sieht es mit Szenario2 aus? Ich werde jetzt aber erst einmal das erste fertig implementieren und mal in der GUI die Anzahl der Empfangenen Bytes ausgeben.
-
Ach noch was:
SerialThread() : listener(new PortListener(this))
Das sollte nciht gehen oder? Da wird der Portlistener ja nciht in einem neuen Thread gestartet oder? ich habe es jetzt so:
class SerialThread : public QThread { public: SerialThread(QString name) { portName=name; } void run(){ listener = new PortListener(portName); exec(); } private: QString portName; PortListener *listener; };
-
Jan++ schrieb:
Ach noch was:
SerialThread() : listener(new PortListener(this))
Das sollte nciht gehen oder? Da wird der Portlistener ja nciht in einem neuen Thread gestartet oder?
Hat doch damit nix zu tun. Solange du nicht im Konstruktor von PortListener schon die Arbeit aufnimmst. Und da würd ich mir lieber eine start()-Methode basteln die erst on demand die Arbeit anstößt.
Schau dir aber unbedingt mal in Debug-Ausgaben an in welchem Thread du gerade bist:
qDebug() << this->thread() << QThread::currentThread() << qApp->thread();
Somit weißt du, ob gerade wirklich im gewünschten Thread gearbeitet wird.
-
Aber wenn ich den Listener im Konstruktor anlege dann Wird er ja schon gestartet, bevor ich den Thread starte.
-
Jan++ schrieb:
Aber wenn ich den Listener im Konstruktor anlege dann Wird er ja schon gestartet, bevor ich den Thread starte.
Das weiß ich nicht ohne Code...
Startest du denn explizit im Konstruktor von PortListener?!?
-
Naja wenn ich im Konstruktor von SerialThread einen PortListener anlege dann horcht der doch schon.
MainWindow:
MainWindow::MainWindow() { setupUi(this); t1 = new SerialThread("/dev/cu.usbserial-ftDIHUS6"); //t1->start(); }
SerialThread:
#ifndef SERIALTHREAD_H #define SERIALTHREAD_H #include <QThread> #include "PortListener.h" #include <qDebug> class SerialThread : public QThread { public: SerialThread(QString name) { portName=name; listener = new PortListener(portName); } void run(){ exec(); } private: QString portName; PortListener *listener; }; #endif // SERIALTHREAD_H
-
Nochmal:
Ich habe keine AHnung was dein PortListener im Konstruktor treibt, ohne Code!!
Gib dem PortListener eine Methode "start", dann kannst du kontrollieren wann er gestartet wird.
-
Der steht doch in Post1 ;-). Ich muss das so und so ncoh ein wenig Umstrukturieren. Wenn ich z.B. auch senden möchte. Da hat serialPort auch eine Methode dafür.
Ich würde dann in der GUI einen Verbinden Button anlegen. Nur müsste ich dann am besten den Port Listener so auslegen, dass er auch senden kann. Denn sonst wird das mit dem öffnen ja Mist. Wenn ich senden will und dazu den Port erneut öffne.
-
Eigentlich könnte ich das ja auch gleich alles von Listener in SerialThread packen, oder?
-
Jan++ schrieb:
Eigentlich könnte ich das ja auch gleich alles von Listener in SerialThread packen, oder?
Weiß ich nicht
Wenn dein SerialThread nur eine Aufgabe besitzt, und zwar ein PortListener-Objekt verwalten und STarten, dann ist dies tatsächlich unnötig.
Soll dein SerialThread mal erweitert werden und nicht nur mit dem einen PortListener-Objekt arbeiten, macht es durchaus Sinn. Aber die Antwort kennst nur duIch würde auch niemals einen Listener oder sonst was gleich im Kostruktor starten, denn du willst ja sicherlich auf Ereignisse (sprich SIGNALS) warten. Wenn das Ding gleich asynchron zu werkeln anfängt kann es passieren, dass du die ersten Signals gar nicht mehr mitbekommst. Deshalb dringend angeraten:
listener = new PortListener(this); connect(listener, SIGNAL(newData()), SLOT(on_listener_newData())); listener->start();
Oder das start() gleich in QThread::run() aufrufen.
Ansonsten: Sry, aber ich erinner mich nicht an Code, wenn mir der Post oder der ganze Thread zu lange wird
-
Gesagt getan:
MainWindow::MainWindow() { setupUi(this); t1 = new SerialThread("/dev/cu.usbserial-ftDIHUS6"); t1->start(); connect(ConnectButton,SIGNAL(clicked()),t1,SLOT(openClose())); }
#ifndef SERIALTHREAD_H #define SERIALTHREAD_H #include <QThread> #include <qDebug> #include "qextserialport.h" class SerialThread : public QThread { Q_OBJECT public: SerialThread(QString name); void run(){ exec(); } private: QString portName; QextSerialPort *port; int Anzahl; private slots: void onReadyRead(); void openClose(); }; #endif // SERIALTHREAD_H
#include "SerialThread.h" SerialThread::SerialThread(QString name) { portName=name; this->port = new QextSerialPort(portName, QextSerialPort::EventDriven); port->setBaudRate(BAUD9600); port->setFlowControl(FLOW_OFF); port->setParity(PAR_NONE); port->setDataBits(DATA_8); port->setStopBits(STOP_1); } void SerialThread::onReadyRead() { QByteArray bytes; int a = port->bytesAvailable(); bytes.resize(a); port->read(bytes.data(), bytes.size()); qDebug() << "bytes read:" << bytes.size(); qDebug() << "bytes:" << bytes; Anzahl+=bytes.size(); } void SerialThread::openClose() { if(!port->isOpen()) { if (port->open(QIODevice::ReadWrite) == true) { connect(port, SIGNAL(readyRead()), this, SLOT(onReadyRead())); qDebug() << "listening for data on" << port->portName(); } else { qDebug() << "device failed to open:" << port->errorString(); } } else { port->close(); if(!port->isOpen()) { qDebug() << "port " << port->portName() << " closed"; } } }
Ein Problem hat das ganze noch. Wenn ich ConnectButton drücke. Wird nochmal ein neuer Thread gestartet. Woran liegt das?
-
Weiterhin würde mich interessieren, wie man das realisieren kann, dass erst mit dem Öffnen der Thread erzeugt wird. Und beim nochmaligen klicken auf den Verbinden Button die Schnittstelle geschlossen, und der Thread beendet wird.
-
Ich habe jetzt nochmal diese Implementierung probiert:
void SerialThread::run() { this->port = new QextSerialPort(portName, QextSerialPort::EventDriven); port->setBaudRate(BAUD9600); port->setFlowControl(FLOW_OFF); port->setParity(PAR_NONE); port->setDataBits(DATA_8); port->setStopBits(STOP_1); if (port->open(QIODevice::ReadWrite) == true) { connect(port, SIGNAL(readyRead()), this, SLOT(onReadyRead())); qDebug() << "listening for data on" << port->portName(); } else { qDebug() << "device failed to open:" << port->errorString(); } exec(); } void SerialThread::onReadyRead() { QByteArray bytes; int a = port->bytesAvailable(); bytes.resize(a); port->read(bytes.data(), bytes.size()); qDebug() << "bytes read:" << bytes.size(); qDebug() << "bytes:" << bytes; }
Da scheint etwas mit den Signalen nicht zu funktionieren, denn wenn ich ein Byte sende kommt:
bytes read: 1 bytes: "j" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: "" bytes read: 0 bytes: ""
-
Warum das jetzt so ist weiß ich nicht (hab mit QextSerialPort noch nix gemacht). Aber du kannst ja nur die empfangenen Daten verarbeiten, wenn size > 0.
Warum jetzt auf einen Button-click ein neuer Thread gestartet werden sollte erschließt sich mir aus dem Code nicht. Woher weißt denn du dass ein neuer Thread gestartet wird? Hast du da sonst noch irgend welche connects?
-
Also in dem einen habe ich keine weiteren connects. Mit dem OpenClose aber anscheinend wird wenn ich OpenClose aus dem Mainthread aufrufe implizit ein neuer Thread gestartet. Entnehmen tue ich das der Prozessanzeige im OS.
Zu dem anderen, wo die Bytes mehrmals kommen da dachte ich halt, ich habe vom Grundaufbau was falsch gemacht.