Arbeitet QThread Obj. mit eigener Eventloop eigenen Slot ab?

Alles rund um die Programmierung mit Qt
Antworten
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Arbeitet QThread Obj. mit eigener Eventloop eigenen Slot ab?

Beitrag von Chris81T »

Hallo zusammen.

Also vorweg zu meinem Anliegen:
In einem SW-Projekt mit 5 Leuten bearbeiten wir eine etwas größere Aufgabe. Mein Teil dabei ist so der "Kern" der Sache. Der die Objekte der Anderen erzeugt und handelt.
Geplant ist es so: Der Mainthread, der mit dem Prozess erzeugt wird, ist für die GUI, also das man dort die Button's usw. bedienen kann und es gibt eine QThread abgeleitete Klasse, die als WorkerThread (mit eigener Eventloop) fungiert. Dieser bekommt von der GUI, oder anderen Objekten meiner Kollegen Jobs in einen Puffer geladen. Der Workerthread schaut mit einem eigenem QTimer immer wieder nach, ob dieser Puffer voll ist, wenn ja, bearbeitet er den Job...
Das funktioniert soweit auch gut, aber habe den Vedacht, dass es irgendwie alles in dem Mainthread ausgeführt wird.

Deswegen habe ich mir ein neues Bsp-Projekt erzeugt, was unabhängig vom laufendem Projekt ist und habe dazu Fragen an euch, bzw. brauch eure Hilfe, um das QThreading auch richtig verstanden zu haben:

Hier die main.cpp:

Es wird ein RF_Core Objekt erzeugt und gestartet. Dann mit app.exec() in die Eventloop gewechselt

Code: Alles auswählen

#include "RF_Core.h"
#include <QtCore>
#include <QApplication>

int main(int argc, char *argv[])
{
	QApplication app(argc, argv);
	RF_Core core;
	core.startCore();
	qDebug("MAIN METHOD:: GOING INTO EVENTLOOP OF MAINTHREAD");
	return app.exec();
}
Hier die Header des RF_Core

Code: Alles auswählen

#ifndef RF_CORE_H
#define RF_CORE_H
#include <QObject>
#include <QTimer>
#include "RF_Thread.h"

class RF_Core : public QObject
{
	Q_OBJECT

private:
	QTimer * mTimer;
	RF_Thread * mThread1;
	int mSwitch;
	
public:
	void startCore();
	
public slots:
	void timerSlot();
	
signals:
	void signalTask2();
	void signalTask3();
};
#endif
Der RF_Core besitzt einen RF_Thread, den er erzeugt und mit ->start() startet. Außerdem gibt es einen eigenen QTimer (singleshot == true), der bei unterschiedlichen Zeiten das timeout() emitiert und den timerSlot() anstößt. In diesem TimerSlot wird entweder die task1, task2 oder task3 des Threads auf unterschiedlichen Varianten zum Testen emittiert, ausgeführt.

Hier die *.cpp des RF_Core

Code: Alles auswählen

#include "RF_Core.h"
#include "RF_Core_Server.h"

void RF_Core::startCore()
{
	qDebug("Core is running");
	
	this->mSwitch = 0;
	this->mTimer = new QTimer();
	QObject::connect(this->mTimer, SIGNAL(timeout()), this, SLOT(timerSlot()));
	
	this->mTimer->setSingleShot(true);
	this->mTimer->start(3500);
	
	this->mThread1 = new RF_Thread(this->parent());
	QObject::connect(this, SIGNAL(signalTask2()), this->mThread1, SLOT(task2()), Qt::QueuedConnection);
	this->mThread1->start(QThread::NormalPriority);
	
	// signal3 mit signal3 des threads verbinden:
	QObject::connect(this, SIGNAL(signalTask3()), this->mThread1, SIGNAL(signalTask3()), Qt::QueuedConnection);	
}

void RF_Core::timerSlot()
{
	qDebug("void RF_Core::timerSlot() >> MAINTHREAD <<");
	if(this->mThread1->isRunning())
		qDebug("Thread is running...");
	else
		qDebug("Thread is NOT running...");
	
	if (this->mSwitch == 0)
	{
		this->mSwitch++;
		qDebug(">>> mSwitch is 0 <<<");
		qDebug("call the task1 method of thread...");
		this->mThread1->task1();
		qDebug("try to start the timer again; 3sec");
		this->mTimer->start(3000);
	}
	else
		if (this->mSwitch == 1)
		{
		this->mSwitch++;
		qDebug(">>> mSwitch is 1 <<<");
		qDebug("emit signal of task2");
		emit this->signalTask2();
		qDebug("try to start the timer again; 1,5sec");
		this->mTimer->start(1500);
		}
		else
			if (this->mSwitch == 2)
			{
			this->mSwitch = 0;
			qDebug(">>> mSwitch is 2 <<<");
			qDebug("emit signal to emit thread- signalTask3()");
			emit this->signalTask3();
			qDebug("try to start the timer again; 2,222sec");
			this->mTimer->start(2222);
			}
}
Hier die Header des RF_Thread

Code: Alles auswählen

#ifndef RF_THREAD_H_
#define RF_THREAD_H_

#include <QThread>
#include <QTimer>

class RF_Thread : public QThread
{
	Q_OBJECT

private:
	int mCount; // einfacher Zähler 
	QTimer *mTimer;
	
	// einfache Zähler der einzelnen "Aufgaben"
	int mTask1; 
	int mTask2;
	int mTask3;
		
public:
	RF_Thread(QObject* parent = 0);
	void run();

private slots:
	void internalTask();
	
public slots:
	void task1();
	void task2();
	void task3();
	
signals:
	void startTimer(int);
	void stopTimer();
	void signalTask3(); // in run() wird ein connect auf den slot task3() gemacht
};
#endif /*RF_THREAD_H_*/
Der RF_Thread besitzt auch seinen eigenen QTimer, der mit timout() immer wieder den Slot internalTask() ansprechen soll, wenn er aktiv ist. Das wird mit 2 Signalen start/stopTimer gemacht. Dieser wird in der run() Methode angelegt und passend verbunden. Danach geht's in die QThread eigene Eventloop mit exec();

Die task1, task2, task3, internalTask machen keine besonderen Aufgaben. Hauptsächlich geht es darum, dass der QTimer ma an/ausgeschaltet wird und man qDebug's der angestoßenen Slot's erhält

Hier der *.cpp des RF_Thread

Code: Alles auswählen

#include "RF_Thread.h"

RF_Thread::RF_Thread(QObject* parent)
	: QThread(parent)
{
	this->mCount = 0;
	this->mTask1 = 0;
	this->mTask2 = 0;	
	this->mTask3 = 0;	
}

void RF_Thread::run()
{
	qDebug("	void RF_Thread::run()");
	qDebug("	Create new Timer & connect it");
	this->mTimer = new QTimer();
	QObject::connect(mTimer, SIGNAL(timeout()), this, SLOT(internalTask()));
	QObject::connect(this, SIGNAL(startTimer(int)), this->mTimer, SLOT(start(int)));
	QObject::connect(this, SIGNAL(stopTimer()), this->mTimer, SLOT(stop()));
	
	// eigenes Signal wird mit eigenem Slot verbunden
	QObject::connect(this, SIGNAL(signalTask3()), this, SLOT(task3()));
	
	this->exec();
}

void RF_Thread::internalTask()
{
	qDebug("								void RF_Thread::internalTask()");
	qDebug("								>> Count: %i, Task1: %i, Task2: %i, Task3: %i", this->mCount, this->mTask1,
																							this->mTask2, this->mTask3);
}

void RF_Thread::task1() // wird "normal" von RF_Core Object aufgerufen
{
	qDebug("						void RF_Thread::task1()");
	this->mCount++;
	this->mTask1++;
	qDebug("emit stopTimer()");
	emit this->stopTimer(); // stoppe den eigenen QTimer
}

void RF_Thread::task2() // wird emitiert
{
	qDebug("						void RF_Thread::task2()");
	this->mCount++;
	this->mTask2++;
	if(!this->mTimer->isActive())
	{
		qDebug("emit startTimer(500)");
		emit this->startTimer(500); // starte eigenen QTimer
	}
}

void RF_Thread::task3() // wird von außen mit Signal über das eigene Signal emitiert
{
	qDebug("						void RF_Thread::task3()");
	this->mCount++;
	this->mTask3++;
	//while(1){}; // Annahme: Thread (this) wäre ausgelastet, aber es sollte eigentlich der Main- Thread
				  // weiterlaufen?! Also qDebug(..)'s ausgeben..
}

So, das läuft soweit ohne Probleme.

Jetzt geht's los:
------------------------------------------------------------------------------------
In dem Qt4 Assistent habe ich folgendes gelesen und deswegen im RF_Core die QueuedConnections angewendet:
Each QThread can have its own event loop. You can start the event loop by calling exec(); you can stop it by calling exit() or quit(). Having an event loop in a thread makes it possible to connect signals from other threads to slots in this threads, using a mechanism called queued connections. It also makes it possible to use classes that require the event loop, such as QTimer and QTcpSocket, in the thread.

Ich wollte nun wissen, ob die task1, "2, "3 und internaltask() auch wirklich vom RF_Thread "bearbeitet" werden, habe in task1, 2, 3 und internalTask() jeweils nen Breakpoint gesetzt und dann (Wir nutzen die Eclipse Europa Umgebung + CDT + Qt Plugin) den gdb Debugger angeworfen.

Dieser zeigt Thread[1] und Thread[2] an. Soweit so gut. Thread1 sollte wohl der Mainthread sein und Thread2 der RF_Thread.

1.
Nun kommt der erste Breakpoint in task1. gdb schreibt
Thread[1](Suspended: Breakpoint hit.)
Thread[2](Suspended)
--> Das ist ja für mich noch plausibel, da der slot direkt über das Objekt aufgerufen wird.

2.
Nun kommt der nächste Breakpoint in task2. gdb schreibt
Thread[1](Suspended: Breakpoint hit.)
Thread[2](Suspended)
--> Das versteh ich nun nicht. Ich habe doch eine QueuedConnection gemacht.

3.
Nun kommt der nächste Breakpoint in task3. gdb schreibt
Thread[1](Suspended: Breakpoint hit.)
Thread[2](Suspended)
--> Auch hier hätte ich gedacht, das Thread2 den Haltepunkt trifft. Es wird ja ein Signal des RF_Cores auf ein Signal des RF_THread verbunden. Und intern im RF_Thread dieses Signal auf den slot task3()
(Halt mal zum Testen, wie es reagiert)

4.
Nach weiteren Haltepunkten kommt dann auch der Haltepunkt in
internalTask(). Was mit dem RF_Core ja nix zu tun hat.
gdb:
Thread[1](Suspended: Breakpoint hit.)
Thread[2](Suspended)
--> Hier hätte ich es mindestens erwartet, das Thread2 reagiert.

Nun gut, jetzt weis ich auch nicht, ob es was mit dem gdb zu tun hat oder so und habe mir folgendes überlegt, um es anders zu testen:
Wenn ich im Task3 des RF_Threads eine while(1) einbaue (Momentan im Code auskommentiert), bin ich von ausgegangen, das RF_Core durch seinen QTimer immer wieder den timerslot() anstößt und der Thread task1 und task2 ausführt und in task3 durch die Endlosschleife ausgelastet ist, aber dann noch mindestens die qDebug's des RF_Core's weiterhin kommen würden, da dieser ja im Mainthread läuft?! Leider kommt kein qDebug mehr :(

Vielen Dank schonmal im Vorraus für eure Hilfe. Was mach ich falsch, oder geh es falsch an :?:

Viele Grüße
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Ich blick da zwar noch nicht so ganz durch (wo z.B. soll dieser "Job-Buffer" sein), aber ich glaube, da fehlt noch völlig das Verständnis für MT-Programmierung...
aber habe den Vedacht, dass es irgendwie alles in dem Mainthread ausgeführt wird.
Naja, wen wundert's:

Code: Alles auswählen

void RF_Core::timerSlot()
{
  [...] 
  this->mThread1->task1();
Welcher Thread ruft den "task1" auf.....?

Code: Alles auswählen

while(1){};
Das macht überhaupt keinen Sinn (egal wo in deinem Projekt). Das sorgt nur dafür, dass ein CPU-Core 100% ausgelastet wird und (aufgerufen durch den Hauptthread, siehe oben) die gesamte GUI einfriert.
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Beitrag von Chris81T »

Also zum Ersten steht da, dass das ein BEISPIEL Projekt ist. Also das hat NIX mit dem eigentlichen Projekt zu tun. Deswegen dort auch kein JobBuffer.

Zum Anderen hab ich ja drei Varianten in dem timerSlot() verwendet!!!

Und steht auch drin:
1.
Nun kommt der erste Breakpoint in task1. gdb schreibt
Thread[1](Suspended: Breakpoint hit.)
Thread[2](Suspended)
--> Das ist ja für mich noch plausibel, da der slot direkt über das Objekt aufgerufen wird.
Okay, falsch ausgedrückt: ist mir nicht nur plausibel, es ist mir klar!! (ist ja nur zum Testen, auch für Einarbeitung mit dem gdb mit eclipse usw.. Bisher nur auf Windows API- Ebene mit VS & Co programmiert und lern gerad Qt zu verwenden.)

Aber was ist denn mit emitierten Signal für task2() und task3() ??

Also nochmal, damit's kein Missverständnis gibt. Das ist nur ein Testprojekt, indem es um einen Thread geht, der die Slots selbst bearbeiten soll.

Hat jemand für dieses Beispiel Rat für mich? Danke.
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Ich würde sagen bau dein Testcase mal etwas einfacher - da kommt ja keiner raus... :(
Das Thread-Objekt lebt immer noch im Main-Thread - was erwartest Du also?
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Beitrag von Chris81T »

Hallo Namensvetter :D
Das Thread-Objekt lebt immer noch im Main-Thread - was erwartest Du also?
:arrow: Vielen Dank, dass gab mir einen Denkanstoß zu:

>> void QObject::moveToThread ( QThread * targetThread )

Ich hab nun ein kleines Bsp. nochmal programmiert. Und beim debuggen macht nun endlich der Thread[2] einen "Breakpoint hit." im slot task() :) .
Ist das so in Ordnung, oder sollte man noch was beachten (will's auch richtig machen):

Viele Grüße!

main.cpp

Code: Alles auswählen

#include "MyThread.h"

#include <QtCore>
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
	
    MyThread * t = new MyThread;
    t->moveToThread(t);
    t->start(QThread::NormalPriority);
    return a.exec();
}
MyThread.h

Code: Alles auswählen

#ifndef MYTHREAD_H_
#define MYTHREAD_H_

#include <QThread>
#include <QTimer>

class MyThread : public QThread
{
	Q_OBJECT
private:
	QTimer * mTimer;
public:
	MyThread(QObject* parent = 0);
	void run();
public slots:
	void task();
signals:	
};
#endif /*MYTHREAD_H_*/
MyThread.cpp

Code: Alles auswählen

#include "MyThread.h"

MyThread::MyThread(QObject * parent)
	: QThread(parent)
{
	qDebug("MyThread::MyThread(QObject * parent)");
}

void MyThread::run()
{
	qDebug("void MyThread::run()");
	this->mTimer = new QTimer();
	
	QObject::connect(this->mTimer, SIGNAL(timeout()), this, SLOT(task()));
	
	this->mTimer->start(1000);
	this->exec();
}

void MyThread::task()
{
	qDebug("void MyThread::task()");  // hier einen Breakpoint gesetzt
}
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

An der Stelle nützt Dir moveToThread auch nichts (denke ich) da der Thread noch nicht läuft. Was Du allerdings jetzt korrekt gemacht hast ist, dass das connect im anderen Thread ist - und deshalb kommt auch der Breakpoint korrekt.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Beitrag von Chris81T »

An der Stelle nützt Dir moveToThread auch nichts (denke ich) da der Thread noch nicht läuft
Also ich hatte die moveToThread() im Qt Assistent gestern abend entdeckt, als du das gemeint hast, dass das Thread-Objekt im Mainthread "lebt".
Also habe ich das Bsp. schnell programmiert und die Funktion erst nicht verwendet. Dann hab ich das mit dem debugger laufen gelassen, der mir dann den Breakpoint bei Thread1 liefert. (nur um gleiche "Problemstellung" wieder zu haben)
Darauf dann die moveToThread() verwendet und wieder mit dem Debugger geschaut. Da kam dann der "Hit" bei Thread2. Also sollte mir das moveToThread da doch schon was nützen?! - Okay, es spricht nichts dagegen, die Methode nach dem laufenden Thread erst zu verwenden, wenn dieser die eigene Eventloop angestoßen hat -
Aber sollte doch so auch funktionieren?

Das connect zu dem Qtimer des Thread's hat ich auch so in dem RiesenBsp (oben) schon so gehabt.

Ich habe das Bsp auch etwas erweitert, so dass ein Objekt, dass im Mainthread lebt, ein Signal an einen Slot des MyThreads schickt. (QueuedConnection dann in der main Methode gemacht, da ja nur dort beide Objekte bekannt sind). Auch hier greift der Breakpoint im MyThread beim debuggen, wie es sein soll.
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Prinzipiell:
Du verschenkst ne ganze Menge zeugs eigentlich ....
Geplant ist es so: Der Mainthread, der mit dem Prozess erzeugt wird, ist für die GUI, also das man dort die Button's usw. bedienen kann und es gibt eine QThread abgeleitete Klasse, die als WorkerThread (mit eigener Eventloop) fungiert. Dieser bekommt von der GUI, oder anderen Objekten meiner Kollegen Jobs in einen Puffer geladen. Der Workerthread schaut mit einem eigenem QTimer immer wieder nach, ob dieser Puffer voll ist, wenn ja, bearbeitet er den Job...
Warum ?
Klar kann man das per hand machen, mit Jobqueues, aber warum dann die QT verwenden ? Wenn man die nicht mal richtig "ausnutzt" ?

Das Ganze queuen macht dir der Signal/Slot Mechanismus .... schickst du nen Event an den Thread, und die Signal/Slot verbindung iss queued, dann sagts der name eigentlich schon, die dinger kommen in ne queue ... warum da dass extra machen ...

Timer um nen Thread schauen zu lassen, ob er was zu tun hat ? das ist fast Polling ^^
1. brauchst du es nicht, da eine eventloop genau das scho fuer dich macht.
2. selbst wenn es selber machen willst, Events waeren der bessere weg :-)

Die gedanken die dir machst, so mit jobqueue und timern, sind soweit ok, "passen" aber nicht zum Framework. Lass das die QT machen, oder benutzt die QT ned, und nimm ne lowlevel API fuer ....

Ciao ...
Chris81T
Beiträge: 82
Registriert: 4. Mai 2008 00:06
Wohnort: Urbar

Beitrag von Chris81T »

@RHBaum:

Es ist das erste Projekt(Studienarbeit) mit Qt. Das Projekt dient ja zum Lernen. Ich gebe dir voll und ganz recht, dass man auch die Qt "Sachen" verwenden soll, und das wird auch beim nächsten Projekt auch angewendet ;) Durch dieses aktuelle Projekt findet man mit der Zeit dann die besseren Lösungen und weis es für's nächste Mal besser :)

Leider gibt es halt ein Zeitproblem wegen auch anstehenden Klausuren und so und kann nicht alles wieder "auf den Kopf" stellen..
Antworten