Seite 1 von 2

Fragen zu QThread und QMutex

Verfasst: 25. August 2009 16:17
von Apollinaris
Hallo,

ich habe eine Frage an euch. Eins vorab, ich benutze Visual Studio 2008 + QT 4.5. Ich möchte zwei Klassen von QThread ableiten. Einmal eine für meine GUI und einmal für meine UDP Kommunikation. Ich habe mir meine GUI mit dem QT-Designer zusammen geklickt. Ich denke das muss nicht kommentiert werden.

Code: Alles auswählen

#include "allneededheader"

class Gui : public QMainWindow
{
	Q_OBJECT

public:
	Gui(QWidget *parent = 0, Qt::WFlags flags = 0);
	~Gui();
private slots:
    void Initialize();
private:
	Ui::GuiClass ui;

	Com *myCom;
};

Gui::Gui(QWidget *parent, Qt::WFlags flags) : QMainWindow(parent, flags)
{
    ui.setupUi(this);

    Gui::connect( ui.btnInitialize, SIGNAL( clicked() ),this, SLOT( Gui::Initialize() ) );
}

void Gui::Initialize()
{
    bool rc;
	
    rc = myCom->initialize( 10 );
}
Jetzt zu meiner Kommunikationsklasse. Die sieht wie folgt aus.

Code: Alles auswählen

class Com : public QObject
{
	Q_OBJECT

public:
	Com(QTextEdit *textEdit);
	~Com();
    bool initialize(unsigned char);
private slots:
	void timeOut();
	void UdpPackage();
private:
	QUdpSocket *udpSocket;
	QTimer *timer;
	QMutex *mutex;
};
Hier im Konstruktor erzeuge ich mir die ganzen Objekte die ich benötige.

Code: Alles auswählen

Com::Com()
{
	udpSocket = new QUdpSocket( this );
	timer = new QTimer( this );
	timer->setInterval( 5000 );

	connect( timer, SIGNAL( timeout() ), this,  SLOT( timeOut() ) );
	connect( udpSocket, SIGNAL(readyRead() ), SLOT( getUdpPackage() ) );

    // Öffne einen Port zum empfangen von Nachrichten
    udpSocket->bind( QHostAddress::QHostAddress( "IP" ), Port);
}
Im Destruktor lösche ich die ganzen Objekte, um Memory Leaks zu verhindern.

Code: Alles auswählen

Com::~Com(void)
{
	udpSocket->close();
	delete udpSocket;
	delete timer;
	delete mutex;
}
In dieser Methode versende ich ein Paket (die Methode werde ich jetzt nicht zeigen), starte den Timer und möchte den Thread mit einer Mutex sperren, bis die Daten angekommen sind oder mein Timer runtergezählt hat.

Code: Alles auswählen

bool Com::initialize( unsigned char INITIALIZE )
{
	sendUdp( INITIALIZE );

	timer->start();

	mutex->lock();

    *
    *
    return rc;
}
Dieser Slot wird aufgerufen, wenn Timer runtergezählt hat. Damit möchte ich das warten auf eine Antwort unterbinden. Anschließend gib ich die Mutex wieder frei.

Code: Alles auswählen

void Com::timeOut()
{
	timer->stop();

    *
    *

	//Mutex wieder freigeben
	mutex->unlock();
}
Wenn daten angekommen sind, öffnet sich dieser Slot und die Mutex wird wieder freigegeben.

Code: Alles auswählen

void Com::UdpPackage()
{
	//Timer stoppen
	if ( timer->isActive() )
		timer->stop();
    *
    *
    *

	//Mutex wieder freigeben
	mutex->unlock();
}
Wenn ich die Mutex entferne klappt das alles. Aber ich möchte eine ganz andere Funktion erreichen. Ich möchte das beide Klassen zwei verschiede Threads sind, aber wenn ich diese noch zusetztlich von QThread ableite. Sagt mir der Kompiler, dass dieses nicht möglich ist, da QMainQindow und QObject bereits Basisklassen von QThread sind. Wie kann ich zwei Thread erzeugen?

Mein zweites Problem ist noch, das ich in der Methode "initialize" von der Kommunikationsklasse nicht auf die Mutex warten kann. Mir ist dabei wichtig, das diese einen Rückgabewert hat, jetzt in diesem Fall ein boolscher Wert, damit ich das erfolgreich oder nicht erfolgreich signalisieren kann. Deswegen möchte ich da warten, ob der Timer oder ein UDP Paket gekommen ist.

Würde mich sehr freuen, wenn ihr mir weiterhelfen könnt oder hoffe auf Anregungen. Vielleicht hat jemand soetwas schon implemetiert und kann mir eine bessere Lösung vorschlagen.

Danke
Gruss
Apollinaris

Ich benutze Visual Studio 2008 + QT 4.5 (open source)

Verfasst: 25. August 2009 16:58
von solarix
Ich möchte zwei Klassen von QThread ableiten. Einmal eine für meine GUI und einmal für meine UDP Kommunikation.
Ich glaube nicht dass du das moechtest... siehe
http://doc.trolltech.com/4.5/threads.ht ... d-qobjects

Aber das macht ja nichts, denn die GUI _IST_ ja schon ein Thread.. also bleibt nur noch _ein_ weiterer, der UDP-Thread..
Mein zweites Problem ist noch, das ich in der Methode "initialize" von der Kommunikationsklasse nicht auf die Mutex warten kann. Mir ist dabei wichtig, das diese einen Rückgabewert hat, jetzt in diesem Fall ein boolscher Wert, damit ich das erfolgreich oder nicht erfolgreich signalisieren kann. Deswegen möchte ich da warten, ob der Timer oder ein UDP Paket gekommen ist.
Falsches Konzept: wenn du daraus ein (Q)Thread machst, musst du ja eh irgendwie dein Zustand an die GUI ubermitteln. Also: da muss nirgends mehr gewartet werden, sondern du sendest mittels eigenen Signals der GUI den Zustand ("ich verbinde mich" , "fehler/timeout", "Verbindung geschlossen" usw).

Ausserdem: Falls du den Thread nur wegen der UDP-Kommunikation moechtest: das braucht es ebenfalls nicht, weil die gesammte Kommunikation ueber Signals behandelt werden kann (readyRead() & CO)


[EDIT]
Sagt mir der Kompiler, dass dieses nicht möglich ist, da QMainQindow und QObject bereits Basisklassen von QThread sind. Wie kann ich zwei Thread erzeugen?
QThread _ist_ ja ein QObject.. weshalb also von beiden erben...

Verfasst: 25. August 2009 17:11
von nuke160
So wie ich dich verstanden habe, willst du einen Thread für deine Kommunikationsklasse COM haben. Das könnte so aussehen:

Code: Alles auswählen

class Com : public QThread
{
   Q_OBJECT

public:
   Com(QTextEdit *textEdit);
   ~Com();
   void run();
    ...
}; 
Zur Funktion void Com::run().
Hier schreibste alles, was der Thread machen soll, wenn er aufgerufen wird. Sobald run() zuende ist, ist der Thread zuende. Wenn du exec() am Ende schreibst, läuft der Thread weiter und kann auf Signale reagieren.
Am Besten du liest dir die QT Doku über Threads durch, die ist ziemlich gut. http://doc.trolltech.com/4.5/qthread.html#details

Achja, man startet den Thread übrigens nicht mit run(), sondern mit start(). Heißt: run() wird automatisch aufgerufen. (Aber das steht ja alles in der Doku)

Gruß
nuke160

Verfasst: 26. August 2009 12:20
von Apollinaris
Okay, ich danke euch für die schnelle Antwort.

Die Gui ist schon ein (Q)Thread. Da brauch ich also nichts mehr erweitern.
Jetzt zur der Kommunikation. Ich leite von QThread ab, damit hab ich quasi auch von QObject mit abgeliten, weil das die Basisklasse ist. So kann ich das Konzept der Signale und Slots nutzen. Sieht jetzt so aus:

Code: Alles auswählen

class Com : public QThread
{
   Q_OBJECT

public:
   Com(QTextEdit *textEdit);
   ~Com();
    void run();
    bool initialize(unsigned char);
    *
    *
private slots:
   void timeOut();
   void UdpPackage();
private:
   QUdpSocket *udpSocket;
   QTimer *timer;
   QMutex *mutex;
};
Hinzu kommt jetzt die Methode run. In ihr soll ich eine exec() Methode aufrufen, damit der Thread weiter läuft und auf meinen Timer und UDP Pakete reagieren.

Code: Alles auswählen

void Com::run()
{
	exec();
}
In der GUI erzuege ich von meiner Kommunikationsklasse eine Instanz und starte den Thread direkt.

Code: Alles auswählen

myCom = new Com;
myCom->start();
Das funktioniert bis hier hin alles super. Hoffe bis hier keinen Fehler gemacht zu haben.

Verfasst: 26. August 2009 12:26
von nuke160
Sieht schonmal ganz gut aus denk ich.

Greetz
nuke160

Verfasst: 26. August 2009 12:37
von solarix
das sieht gefährlich aus...

Folgende Punkte:
1. Ein Thread darf keine GUI-Operationen durchführen. Solche Sachen wie "Com(QTextEdit *textEdit);" sind verboten. Richtig wäre, wenn der Text in der GUI ändert, den neuen QString mittels SIGNAL an den Thread zu senden (und umgekehrt).

2. zu "void Com::run() { exec(); }" denke daran: alle QObjecte, welche diesen Eventloop verwenden sollen, müssen auch im Thread leben (z.B. in der run-Mehtode erstellt werden)! Verwende also soweit möglich keine Pointer, sondern lokale Instanzen. Z.B:

Code: Alles auswählen

void ...:run() {
  // Thread-Init:
  QTimer timer;
  connect(&timer,......);
  udpSocket = new .....
  connect(udpSocket,....);

  // run Eventloop
  exec();
}
ich weiss es ist vergebens, trotzdem nochmals den Hinweis: Falls du keine grossen Datenmengen (z.B. Bilder) verarbeiten musst, brauchst du gar kein Thread. Die Realisierung mittels Events gibt viel weniger Probleme als Threads..

Verfasst: 26. August 2009 12:44
von Apollinaris
Aber jetzt bleibt noch das Problem mit der QMutex. Im Konstruktor meiner Kommunikationsklasse erzeuge ich eine Mutex und initialisiere diese mit "NonRekursiv", um einmalig locken zu können.

Code: Alles auswählen

mutex = new QMutex( QMutex::Recursive );
mutex->lock();
In der Methode "initialize" möchte ich nach dem Versenden der Daten hier warten, bis mein Timer oder ein UDP Paket angekommen ist und die Mutex wieder unlockt. Diese Methode läuft weiter und kann einen Rückgabewert zurück senden.

Code: Alles auswählen

bool Com::initialize( unsigned char INITIALIZE )
{
   sendUdp( INITIALIZE );

   timer->start();

   qDebug << " vor dem lock ";
   mutex->lock();
   qDebug << " nach dem lock ";

    *
    *
    return rc;
} 
Deswegen wollte ich dies mit einer Mutex locken, weil doch dann der Thread hier unterbrochen wird. Um dies zu überprüfen hab ich zwei Ausgaben gemacht. Aber dies funktioniert nicht.

Code: Alles auswählen

void Com::timeOut()
{
   timer->stop();

    *
    *

   mutex->unlock();
} 
void Com::UdpPackage()
{
   //Timer stoppen
   if ( timer->isActive() )
      timer->stop();
    *
    *
    *

   //Mutex wieder freigeben
   mutex->unlock();
} 
Ist das der falsche Ansatz mit der Mutex? Mir ist wichtig, da zur warten, um Anschließend einen Rückgabewert zu haben, der soll signalisieren, ob es geklapt hat oder wieso nicht. Hier zu testzwecken nur ein boolscher Wert. Die Kommunikation wird irgendwann nicht mehr von der Gui genutzt, wo man es direkt sehen kann und reagieren kann. Dies möchte ich nach meinen Tests automatisieren.

Verfasst: 26. August 2009 13:04
von Christian81
Ich kapiere nicht wieso man in diesem Beispiel überhaupt einen Mutex braucht... es läuft doch eh in einem Thread.

Verfasst: 26. August 2009 13:06
von solarix
überleg doch mal.. wenn du dann schreibst:

Code: Alles auswählen

myCom = new Com;
myCom->start(); 
if (myCom->initialize(''))
 ...
dann kann das gar nicht funktionieren, weil deine Applikation im zweiten lock() hängt, jedoch der Thread nie gestartet wird (das wird er erst im Eventloop).

Code: Alles auswählen

myCom = new Com;
myCom->start();          // starte später den Thread
myCom->initialize('');   // Hinterlege einen Auftrag fuer die Initialisierung
myCom->waitForInit(); // Eventloop ausführen und solange warten, bis der Thread gestartet wurde und den Job (die Initialisierung) fertig hat
if (myCom->initDone()) // Frage Resultat ab
 ...
Wichtig: die initialize()-Methode führt _keine_ Initialisierung durch, sondern hinterlegt lediglich die Infos für die run()->Methode. Der Zugriff auf die privaten Variabeln muss dann jedoch ebenfalls wieder durch ein Mutex geregelt werden!

Verfasst: 26. August 2009 15:50
von Apollinaris
Okay, das kann nicht funktionieren. Aber eine Frage zu deinem Code

Code: Alles auswählen

myCom = new Com;
myCom->start();          // starte später den Thread
myCom->initialize('');   // Hinterlege einen Auftrag fuer die Initialisierung
myCom->waitForInit(); // Eventloop ausführen und solange warten, bis der Thread gestartet wurde und den Job (die Initialisierung) fertig hat
if (myCom->initDone()) // Frage Resultat ab 
Am Anfang erzeuge ich mir eine Instanz und Anschließend starte ich den Kommunikationsthread. Bis dahin komme ich noch mit. Bei der Methode "initialize" Hinterlege ich einen Auftrag, aber wie implementiere ich das? Ich hab jetzt gedacht, ich rufe die Methode auf und intern wird ein UDP Paket versendet. Aber das wird nicht funktionieren, weil da stell im mir gleich die nächste Frage, erzeuge ich dann noch einen eventloop und wie warte ich auf einen Eventloop? Ist die Methode "initDone" eine getter-Methode?

Würde mich sehr freuen, wenn ihr mir weiterhelfen könnt mit ein paar Tipps.

Verfasst: 26. August 2009 17:09
von solarix
Naja.. wir wissen ja nicht so genau, was das teil tun soll.. ich habs daher offen gelassen..

Etwas konkreter:
- in initialize() setzt du lediglich private Variabeln (z.B. diesen Char.. was auch immer du dann damit machst..)
- in der run()-Mehode (da läuft ja der Thread) kannst du diese privaten Variabeln auslesen und HIER die UDP-Init-Sequenz durchackern..

Was "waitForInit()" hängt nun ein wenig davon ab, ob du nun hier Mutexe brauchen möchtest oder nicht.. denn auf Jeden Fall musst du irgendwann darauf warten, bis der Thread anspringt.
Daher einfach ein Vorschlag:

- waitForInit() führt periodisch aus: 1.Qts-Eventloop (processEvents() der QApplication), 2. Prüfen eines internen Flags, ob die run()-Methode fertig ist (bool) und 3. warten einiger Millisecs (sonst hast du 100% CPU-Last während dieser Zeit)
- initDone() ist nur ein "get" welches das Resultat (Flag oder Fehlermeldung) der run()->Methode abholt.

Das hässlichste daran ist dieses Warten mit Eventloop und sleep, aber bei einem synchronen Unit-Test wirst du nicht daran vorbeikommen..

Verfasst: 28. August 2009 15:30
von Apollinaris
Hallo,

Wofür soll das ganze sein, ja um einen 8Bit Mikrocontroller anzusteuern. Deswegen schicke ich nur einen Byte, um auf der anderen Seite es einfacher zuhaben.

Ich hab das so versucht umzusetzten. So würde das dann ein Teil davon aussehen.

Code: Alles auswählen

#define DONOTHING    0
#define TIMEOUT         1
#define UDPPACKET     2
#define ERROR            3
#define INIT                4

Com::Com()
{
    udpSocket = NULL;
    timer = NULL;

    flag = DONOTHING;
}

Com::~Com(void)
{
   udpSocket->close();
   delete udpSocket;
   delete timer;
}

void Com::initialize( )
{
    flag = INIT;
}

void Com::run()
{
    // Thread-Init:
    if (udpSocket == NULL){
        udpSocket = new QUdpSocket;
        connect( udpSocket, SIGNAL(readyRead() ), this, SLOT( getUdpPackage() ) );
        }

    if (timer == NULL){
        timer = new QTimer( );
        connect( timer, SIGNAL( timeout() ), this,  SLOT( timeOut() ) );
        timer->setInterval( 5000 );
    }
    udpSocket->bind(  85 );

    while(1){
        // run Eventloop
        //exec();

        if (flag == INIT){

            timer->start();

            udpSocket->writeDatagram("10", QHostAddress::QHostAddress("IP"), 85);

            flag = DONOTHING;
        }
    }
}
void Com::timeOut()
{
    timer->stop();

    flag = TIMEOUT;
}

void Com::getUdpPackage()
{
    if ( timer->isActive() )
      timer->stop();

    QByteArray datagram;

    udpSocket->readDatagram( datagram.data(), datagram.size());

    //Paket auswerten

    flag = UDPPACKET;
}
Hab aber jetzt noch eine Frage. Wenn ich das so implementiere, gehen die Signale/Slots nicht mehr. Dafür brauche ich exec(); in meiner run(); Methode. Wenn ich das in der while Schleife positioniere, passiert nichts mehr in der Schleife. Wo platziere ich das oder gibt es eine andere Lösung.

Verfasst: 28. August 2009 15:38
von Christian81
Was ist gegen exec() einzuwenden? Du kannst doch einfach alles im timeout() machen was Du sonst in der komischen Schleife machst...

Verfasst: 28. August 2009 15:46
von Apollinaris
Was ist gegen exec() einzuwenden? Nichts! Ich habe doch geschrieben, wenn exec() drin ist, das nichts mehr in der "komischen" Schleife passiert.

So jetzt noch einmal die Bitte von mir, mir einen Tipp zu geben. Ich brauche ja exec() in run(), sonst gehen die Signale und Slots nicht mehr. Aber soll nur exec() und die initialisierung in run() sein. Aber dann wäre es genauso wie vorher.

Verfasst: 28. August 2009 16:32
von Apollinaris
Ich habe eine Lösung für mein Problem gefunden. Wenn ich auf ein Signal von einen Timer oder einen UdpSocket in dieser Kommunikationsklasse warte. Mach ich ja diesen exec() aufruf in meiner komischen Schleife, um Signale von meinen lebendigen Objekten in der Eventloop empfangen zu können. Wenn dann ein Signal vom Timer oder UdpSocket gekommen ist, rufe am Ende im entsprechenden Slots die Methode quit() auf, damit ich den Eventloop wieder verlasse.

Code: Alles auswählen

void Com::run()
{
    // Thread-Init:
    if (udpSocket == NULL){
        udpSocket = new QUdpSocket;
        connect( udpSocket, SIGNAL(readyRead() ), this, SLOT( getUdpPackage() ) );
        }

    if (timer == NULL){
        timer = new QTimer( );
        connect( timer, SIGNAL( timeout() ), this,  SLOT( timeOut() ) );
        timer->setInterval( 5000 );
    }
    udpSocket->bind(  85 );

    while(1){
        if (flag == INIT){

            timer->start();

            udpSocket->writeDatagram("10", QHostAddress::QHostAddress("IP"), 85);

            flag = DONOTHING;

            exec();
        }
    }
} 
// ---------------------------------------------------------------------------------------
void Com::timeOut()
{
    //Timer stoppen
    timer->stop();

    flag = TIMEOUT;

    quit();
}
// ---------------------------------------------------------------------------------------
void Com::getUdpPackage()
{
    //Timer stoppen
    if ( timer->isActive() )
      timer->stop();

    flag = UDPPACKET;

    QByteArray datagram;

    udpSocket->readDatagram( datagram.data(), datagram.size());

    // Antwort auswerten...

    quit();
}