QThread: Funktionsweise

Alles rund um die Programmierung mit Qt
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Leite von QTread ab und innerhalt von run() erzeugst Du Deinen Parser. Somit läuft der Parser im Thread.
Das Thread-Objekt ist im Hauptthread solange Du nicht innerhalb von run() moveToThread() aufrufst.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Sorry dass ich nicht mehr geantwortet habe... war am Sachsenring unterwegs (die Ohren klingeln jetzt noch)
Illuminatus hat geschrieben:Muss man den Thread Threadkontext (QThread *mThread) auch starten?
Ja... selbstverständlich. Der Slot deines Parsers soll ja via Eventloop des Threads (exec() in der Default-run-Methode) aufgerufen werden.
Illuminatus hat geschrieben:Ja aber laut dem Post vor einigen Tagen, is das der "falsche weg" einen thread zu implementieren oder hab ich das falsch verstanden??? Ich kann die doku nicht mit dem movetothread zusammenbringen
Nein, du hast das schon richtig verstanden. Du bist auch nicht der einzige, den das verwirrt.. Das Problem ist, dass hier die Doku von der Meinung einiger Entwickler bei Nokia abweicht.

hth!
MisterJ
Beiträge: 21
Registriert: 20. November 2008 12:21

Beitrag von MisterJ »

Ich habe ähnliche Probleme wie Illuminatus.

Ich habe mir auch den "richtigen" Weg zur Erstellung eines separaten Threads durchgelesen und habe es wie folgt realisiert:

Threadtest_5.h:

Code: Alles auswählen

#ifndef THREADTEST_5_H
#define THREADTEST_5_H

#include <QtGui/QMainWindow>
#include "ui_threadtest_5.h"
#include <QThread>
#include "Worker.h"

class Threadtest_5 : public QMainWindow
{
	Q_OBJECT

public:
	Threadtest_5(QWidget *parent = 0, Qt::WFlags flags = 0);
	~Threadtest_5();
	QThread * context;
	Worker * worker;

private:
	Ui::Threadtest_5Class ui;
};

#endif // THREADTEST_5_H
Threadtest_5.cpp:

Code: Alles auswählen

Threadtest_5::Threadtest_5(QWidget *parent, Qt::WFlags flags)
	: QMainWindow(parent, flags)
{
	ui.setupUi(this);
	context = new QThread();
	worker = new Worker();

	worker->moveToThread(context);
	connect(ui.pb_Start, SIGNAL(clicked()), worker, SLOT(doWork()));
	//context->start();
}

Threadtest_5::~Threadtest_5()
{
}
Worker.h:

Code: Alles auswählen

#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker :
	public QObject
{
public:
	Worker(void);
	~Worker(void);
public slots:
	void doWork();
};

#endif
Worker.cpp:

Code: Alles auswählen

#include "Worker.h"

Worker::Worker(void)
{
}

Worker::~Worker(void)
{
}

void Worker::doWork()
{
	while(true);
}
Allerdings springt er bei mir gar nicht erst in die while(true) Schleife hinein. Auch wenn ich nach dem connect() mit context->start() die Eventloop starte ändert sich nichts am Verhalten.
Wenn ich das SIGNAL(clicked()) zunächst auf eine Funktion in Threadtest_5 lege und von da aus die doWork()-Funktion aufrufe springt er zwar in die Schleife, meine GUI ist danach aber eingefroren.
Des Weiteren ist die Frage nach dem Verbleib des Worker-Objekts (keine Ableitung der QThread-Klasse sondern eine Ableitung der QObject-Klasse) noch nicht genau beantwortet. Dies würde mich aber auch noch interessieren.

Wenn mich einer darüber aufklären könnte, wäre ich demjenigen sehr dankbar.

Johannes
Dateianhänge
Threadtest.7z
(1.13 KiB) 98-mal heruntergeladen
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

MisterJ hat geschrieben:Wenn ich das SIGNAL(clicked()) zunächst auf eine Funktion in Threadtest_5 lege und von da aus die doWork()-Funktion aufrufe springt er zwar in die Schleife, meine GUI ist danach aber eingefroren.
Das liegt daran, dass die Funktion in dem Thread abgearbeitet wird, in dem sie aufgerufen wurde. Soll heißen: Dein clicked() ruft den SLOT von Threadtest_-Objekt im Mainthread auf. In dem SLOT rufst du doWork() auf, doWork läuft damit im MainThread - und blockiert die Gui.
Du musst jetzt nur schauen, dass der SLOT doWork() im neuen Thread ausgeführt wird - eine einfach AutoConnection tut es da (wie du es im Code auch stehen hast).
Dein Problem: Du startest den context-QThread nicht (warum ist das eigentlich auskommentiert?). Mach das so

Code: Alles auswählen

   worker->moveToThread(context);
   context->start();
   connect(ui.pb_Start, SIGNAL(clicked()), worker, SLOT(doWork()));
und es sollte eigentlich passen. Wenn nicht häng ein minimales, kompilierbaresB Beispiel an, damit wir an deinem Code besser rumsezieren können...
MisterJ
Beiträge: 21
Registriert: 20. November 2008 12:21

Beitrag von MisterJ »

Hallo franzf,

danke für die Antwort. Ich hatte an meinen letzten Beitrag schon die Dateien meines Minimalbeispiels angehängt:

threadtest_5.ui
main.cpp
threadtest_5.h
threadtest_5.cpp
Worker.h
Worker.cpp

Reichen diese aus?

Ich habe die Auskommentierung der context->start()-Funktion wieder rückgängig gemacht und auch an genau die Stelle, die du mir beschrieben hast, gesetzt. Trotzdem wird die Schleife nicht ausgeführt. Die Änderung habe ich jetzt nicht noch einmal neu hochgeladen, daher ist dies in der threadtest_5.cpp noch nicht so implementiert.

Viele Grüße,
Johannes
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Das kommt davon, wenn man das Q_OBJECT in der Klassendefinition vergisst :P

Code: Alles auswählen

class Worker :
	public QObject
{
    Q_OBJECT  // WICHTIG WICHTIG WICHTIG :P
public:
	Worker(void);
	~Worker(void);
public slots:
	void doWork();
};
Worker.h muss dann natürlich auch im .pro unter HEADERS stehen.
MisterJ
Beiträge: 21
Registriert: 20. November 2008 12:21

Beitrag von MisterJ »

Hach :shock:

Vielen Dank!
Manchmal sind es die kleinen Dinge, die einem große Probleme machen.

Wenn ich es richtig verstehe habe ich in der worker-Klasse dann aber keine EventLoop mehr, oder? Müsste ich also einen eventuellen QTimer dann auf dem GUI-Thread laufen lassen? Ich hab das auch schon implementiert und es läuft auch, aber ist das auch die "schönste" Methode?
Des Weiteren kann man anscheinend auch die Funktion nicht einfach per worker->doWork() aufrufen, da sie dann wieder auf dem aufrufendem Thread (GUI-Thread) läuft. Sehe ich das richtig, oder gibt es außer dem Signal - Slot Prinzip noch andere Möglichkeiten eine Funktion, die auf einem anderen Thread mit dieser "richtigen" Methode läuft, aufzurufen?

Vielen Dank nochmal!
Johannes
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

MisterJ hat geschrieben:Wenn ich es richtig verstehe habe ich in der worker-Klasse dann aber keine EventLoop mehr, oder? Müsste ich also einen eventuellen QTimer dann auf dem GUI-Thread laufen lassen?
Sicher hast du eine EventLoop - die im context-Thread. Gestartet wird die mit start() :) So kann dann auch ein Timer über den neuen Thread abgearbeitet werden.
Des Weiteren kann man anscheinend auch die Funktion nicht einfach per worker->doWork() aufrufen, da sie dann wieder auf dem aufrufendem Thread (GUI-Thread) läuft. Sehe ich das richtig, oder gibt es außer dem Signal - Slot Prinzip noch andere Möglichkeiten eine Funktion, die auf einem anderen Thread mit dieser "richtigen" Methode läuft, aufzurufen?
Klar. Geht aber auch nur mit Qt-Mechanismen:
QMetaObject::invokeMethod()
MisterJ
Beiträge: 21
Registriert: 20. November 2008 12:21

Beitrag von MisterJ »

Hallo franzf,

dass man eine EventLoop auf dem context-Thread laufen hat, erschließt sich mir. Das finished()-Signal, das am Ende der überschriebenen run()-Funktion emitiert wurde geht damit aber verloren, oder? Man müsste quasi am Ende einer aufgerufenen Funktion wiederum ein Signal emitieren, dass dieses ersetzt.

In der worker-Klasse habe ich nun in der doWork()-Funktion vor die while(true)-Schleife einen QTimer gesetzt, der einmalig nach 2 Sekunden eine Funktion auslöst. Diese wird aber nicht ausgelöst. Liegt das daran, dass der Thread mit der Schleife beschäftigt ist? Ich dachte, die QTimer würden im Prinzip nochmal in einem eigenen Thread im Hintergrund laufen.

Worker.h

Code: Alles auswählen

#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QTimer>

class Worker :
	public QObject
{
	Q_OBJECT
public:
	Worker(void);
	~Worker(void);
	QTimer * timer;
public slots:
	void doWork();
	void timeoutFunction();
};

#endif
Worker.cpp

Code: Alles auswählen

#include "Worker.h"

Worker::Worker(void)
{
}

Worker::~Worker(void)
{
}

void Worker::doWork()
{
	timer = new QTimer;
	QTimer::singleShot(1000, this, SLOT(timeoutFunction()));
	while(true);
}

void Worker::timeoutFunction()
{
}
Und noch eine Frage zum Schluss:
Wenn eine Funktion aus einem Thread eine Funktion aus der GUI so schnell hintereinander aufruft, dass in der Zwischenzeit die GUI-Funktion nicht beendet werden kann, wird dann eine zweite Instanz dieser Funktion ausgeführt, wartet der Funktionsaufruf, bis die vorherige Funktion beendet wurde und startet dann, oder bekriegen sich beide Funktionsaufrufe?

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

Beitrag von solarix »

MisterJ hat geschrieben:...Das finished()-Signal, das am Ende der überschriebenen run()-Funktion emitiert wurde geht damit aber verloren, oder? Man müsste quasi am Ende einer aufgerufenen Funktion wiederum ein Signal emitieren, dass dieses ersetzt.
Es geht nichts verloren.. der Thread emittet dieses Signal noch immer... nur: wenn dein Slot fertig ist läuft der Thread halt noch immer.. daher hast du schon recht: eigenes Signal "arbeitIstFertig".
MisterJ hat geschrieben: In der worker-Klasse habe ich nun in der doWork()-Funktion vor die while(true)-Schleife einen QTimer gesetzt, der einmalig nach 2 Sekunden eine Funktion auslöst. Diese wird aber nicht ausgelöst. Liegt das daran, dass der Thread mit der Schleife beschäftigt ist?
Ja.. genau daran liegt es.
MisterJ hat geschrieben: Ich dachte, die QTimer würden im Prinzip nochmal in einem eigenen Thread im Hintergrund laufen.
Falsch gedacht.. die Timer brauchen die Eventloop. Wenn du in der GUI "while (1);" schreibst, gehen die (GUI-)Timer genau so wenig wie wenn du im Thread "while (1);" ausführst..
MisterJ hat geschrieben: Wenn eine Funktion aus einem Thread eine Funktion aus der GUI so schnell hintereinander aufruft, dass in der Zwischenzeit die GUI-Funktion nicht beendet werden kann, wird dann eine zweite Instanz dieser Funktion ausgeführt, wartet der Funktionsaufruf, bis die vorherige Funktion beendet wurde und startet dann, oder bekriegen sich beide Funktionsaufrufe?
Das ist eine komische Frage welche zeigt, dass du Threads noch nicht so ganz verstanden hast... Wenn ein Thread eine Methode aufruft, läuft die halt in dessen Kontext. Du kannst eine Methode nicht "schneller als sie beendet wurde" aufrufen. Allerdings kann die gleiche Methode von zwei unterschiedlichen Kontexten aufgerufen werden (von der GUI und vom Thread gleichzeitig). Was dann geschieht hängt von der Methode ab.

Aber designtechnisch ist das eh falsch: ein Thread darf in der GUI nicht herumfummeln. Er darf ein Signal senden welches die Thread-Resultate gleich mitliefert oder die GUI zu irgendwas bewegt (Statusanzeige ändern, Resultate abholen oder sonst was).

hth..
MisterJ
Beiträge: 21
Registriert: 20. November 2008 12:21

Beitrag von MisterJ »

Vielen Dank,

die Antworten haben mir sehr weitergeholfen :D

Johannes
Antworten