Netter Blog zum Thema QThread: You're doing it wrong

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

Netter Blog zum Thema QThread: You're doing it wrong

Beitrag von Chris81T »

Hab ich heut morgen per Zufall entdeckt. Dort wird in einem Qt Block festgehalten, wie man es sich dort vorstellt die Klasse QThread zu verwenden.

http://labs.trolltech.com/blogs/2010/06 ... -it-wrong/

Vielleicht interessiert es jemanden, hilft jemanden weiter...
Sebastian Barth
Beiträge: 9
Registriert: 10. Mai 2010 10:45

Beitrag von Sebastian Barth »

Ich finde den "richtigen Weg" echt toll! So will ich das haben!
Jedoch ergibts sich für mich da ein großes Problem:

Früher, auf dem "falschen Weg" lief das so:

Code: Alles auswählen

// In der vererbten QThread Klasse "WorkerThread":
void run()
{
    Worker worker;
    worker.connect(.....);

    exec();
}

Code: Alles auswählen

// In der instanzierenden Klasse, dessen Object auf einem anderen Thread liegt:
void erstelleThread()
{
    this->workerThread = new WorkerThread(this);
}
Dabei wurde die Workerklasse local in der run() und so auf dem neuen Thread erstellt. Beim Beenden des gestarteten Threads (nicht Zerstören des QThread-Objekts), wird run() verlassen und das Workerobjekt, da local, automatisch aus dem Speicher entfernt.

Nun jedoch, auf dem "richtigen Weg", muss ich die Workerklasse mit new und innerhalb des Kontexts starten, in dem ich sonst nur meinen Workerthread erstellt habe, damit dieses beim Verlassen der Funktion erstelleThread() nicht gleich wieder zerstört wird:

Code: Alles auswählen

// In der instanzierenden Klasse, dessen Object auf einem anderen Thread liegt:
void erstelleThread()
{
    this->workerThread = new QThread(this);
    this->worker = new Worker();
    this->worker->connect(...);
    this->worker->moveToThread(this->workerThread);
}
Wird jetzt das den Thread instanzierende Objekt gelöscht, entfernt es automatisch auch das QThread Objekt. Das Workerobjekt jedoch, bleibt im Speicher und ich muss mich nun selber darum kümmern, es mit delete im Konstruktor der instanzierenden Klasse zu löschen.

Und genau das finde ich doof!

Gerne würde ich das Parent-Child-Sytem dafür weiterhin nutzen, kann es jedoch nicht, da es nicht erlaubt ist, dass ein QObject ein Vaterelement auf einem anderen Thread hat.

Ein Vorschlag von mir:
QObjects dürfen als einzige Ausnahme QThread-Objekte als Vaterobjekt haben, damit diese automatisch beim Zerstören ihres QThread-Objekts gelöscht werden.

Des weiteren fände ich es toll, wenn ein Aufruf des Destruktors eines QThread-Objekts, das Objekt erst dann aus dem Speicher löscht (Aufruf beendet), wenn dessen gestarteter Thread auch beendet ist.

Was sagt ihr dazu?
Irgendwelche Ideen?


Grüße,
Sebastian
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Was sagt ihr dazu?
Was soll ein Parent machen, dessen child komplett(also mit konstruktor/destruktor) in nem anderen thread erstellt wurde ?
"Die instanz" des childs loeschen obwohl vielleicht sogar der thread zum child noch rennt ? Die armen ressourcen , das arme Programm, wird sich wohl nie wieder freiwillig beenden ^^

Die trennung an der stelle iss imho sinvoll !

Normal ist das die Threadinstanz einer QThread/ oder selbst geschriebenen Klasse in 2 Thread-Contexten laeuft.
Und man sollt genau wissen, welche methode in welchen ThreadContext laeuft.
Der Konstruktor/Destruktor und paar hilfsfunktionen im Orginalthread,
die run mthode + aufgerufenen funktionen methode dann im Context des neuen threads.

Es ist imenz wichtig, das Objecte nicht in dem einem Context erzeugt, und im anderen zertoert werden. Das macht man normal in reinem c++ schon ganz ungern (new delete) und erst recht ned bei qt klassen, wo womoeglich noch beim Dtor ereignisse in die eventloop geschrieben werden, bzw signale/slots aufgerufen werden.

Also brauchst du "andere objecte" im run ... hasst du nur 2 möglichkeiten:

du erzeugst das Object selber also local, so das der thread es vorm auslausaufen selber wieder deleted... (oder automatisches Object aufm threadstack halt)

oder du erzeugst dein Object brav im Hauptthread und rufst in der run methode nur eine (threadsichere) Methode an der Klasse auf ...

Alles andere iss im Sinner der threadsicherheit, raceconditions etc ned haendelbar, bzw führt zu undefinierten verhalten.

Ich selber empfinde die QOBject Hirarchie, c++ designtechnisch als Kritisch. Es verstoesst eigentlich gegen c++ coding style regeln (new / delete )
Aber im Context eines Frameworks ungeheuer praktisch. Hier hat also der Komfort ueber das design gesiegt, zu recht. Du kannst aber davon ausgehen, das das auch probleme macht / machen kann. Im Falle von threads z.b. :-) Deshalb die Einschraenkungen halt, das es funktioniert.

Ciao ...
Sebastian Barth
Beiträge: 9
Registriert: 10. Mai 2010 10:45

Beitrag von Sebastian Barth »

Ich sehe keinen Grund, warum der QThread selber nicht der Vater, obwohl dieser auf einem anderen Thread liegt, von einem Workerobjekt, dass auf dem von ihm gestarteten neuen Thread läuft (also ihm gehört), sein darf!

Mag sein, dass es diesen C++ Coding Standard gibt, dennoch sehe ich auch hier keinen Grund, warum Qt diesen nicht erweitern, bzw. einen Parallelen dazu bilden darf. Ich finde auch, dass die Zusammenarbeit von Qt und anderen Frameworks, welche nach den von dir genannten C++ Standards geschrieben sind, problemlos funktioniert.

Ein QThread ist bereits die Verbindung zwischen zwei Threads. Ich finde, dass soll so auch bleiben. Dazu aber, sollte der QThread sich als das übergeordnete Vaterelement verhalten und seine Kinder, die, wie ich finde, die Objekte von dem von ihm gestarteten Thread sind, deleten dürfen und müssen.
Andernfalls ist QThread eine riesige und problematische Lücke des "Qt Coding Standards für C++".

Sebastian
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Andernfalls ist QThread eine riesige und problematische Lücke des "Qt Coding Standards für C++".
Ich geb Dir recht, zum QT Style wuerde die sache mit dem parent-child beziehungen threadübergreifend sicher passen !
Auf der anderen Seite bin ich mir sicher, wenn es so einfach ginge, haette es Trolltech sicher auch so implementiert.
Ich sehe keinen Grund
Kann es sein das du mit Threads noch ned allzuviel gemacht hasst ?
Nen Tipp:
Versuch dich mal nativer mit Threads zu beschaeftigen, also threads mit dem OS zu erstellen und es nicht QT machen zu lassen ...
Bau mal Threads, die in endlos schleifen laufen, und sich auf "Commando" selbst beenden muessen (Terminate thread iss unter einigen Plattformen nur ne kruecke mit vielen unberechenbaren nebeneffekten, ergo sollte nur in absoluten notfaellen aufgerufen werden !)

Threads die einfach durch die threadproc laufen, sind absolut langweilig :-) Intressant wirds erst, wenn du threads von aussen kontrolliert beenden musst(ohne terminate) ... Wenn Du threads synchronisieren musst.

Und dann kommst du zum eigentlichen Problem.
Dein QThread ist nur eine verwaltungsklasse um den Thread drumherum.
Der thread selber iss ne OS ressource.
Alles was ein thread erzeugt, wird der thread wahrscheinlich auch brauchen.
Der logische Umkehrschluss ist, wenn du eine ressource(Klasse) aus dem thread erstellst, sollte sichergestellt werden, das der Thread die ressource nimmer braucht.
Wer kann das besser als der thread selber ?
Dein hauptproblem mutiert aber weiter ....
Wird der destruktor einer klasse aufgerufen, die in einem anderen thread erstellt wurde ... wie stellst du sicher, das der thread nimmer laeuft ?
Kannst du das ueberhaupt(noch) an der stelle ?
Was ist, wenn der thread doch noch laueft ? kannst du nen DTor auf wartescheife schicken ?
Kannst du den User aufbuerden, kontrolle ueber die Aufrufhirarchie zu behalten und deadlocks zu vermeiden ?
Das bei nem mechanismus wo das Loeschen an sich eh deligiert(verborgen) wird. Hat der user ueberhaupt noch die chance, die ablaeufe zu kontrollieren ?

Das war nur die c++ Sicht auf die Logic der Threads.

Dann spielt noch das OS hinein.
Threads haben mit c++ nix zu tun. threads sind eine OS ressource.
der c++ standard kenn (noch) keine threads.
Es kann ressourcen vom OS geben, die threadspezifisch sind. Einige COM -Componenten z.b.
Die darf man multithreaded verwenden, aber per definition muss das Erzeugen und das freigeben im selben thread geschehen.
Spaetestens wenn du diese Dinger in Klassen packst, hasst du genau diese einschraenkung an der backe !

Die Einschraenkung mit das parent client auf einen thread beschraenkt ist, klingt vielleicht hart, erspart aber QT ne menge aerger und overhaed.
Und der Workaround fuer dich ist mega easy !
Du willst ressourcen im parent -client mechanismus haben
Du willst die ressourcen im thread nutzen ?
benutzen schliesst erzeugen nicht mit ein !!!
Warum erzeugst du die Ressource (klasse) ned im Context des erzeugenden Threads, also im CTor der QTHreadklasse.
Dann kannst das deleten ruhig ueber den parent client mechanismus laufen lassen !
Im thread selber erzeugst die klasse nimmer sondern rufst nur methoden an ihr auf !!!
Das ist inntuitiv und soweiso gaengige Praxis bei threadprogrammierung !
Warum willst du um Himmelswillen eine Klasse die an den thread gebunden iss deren erzeugung in den Thread verlegen, anstatt sie einfach an die Verwaltung des Threads zu legen (der thread wird doch eh von nem anderem thread verwaltet) ???

Ciao ....
Sebastian Barth
Beiträge: 9
Registriert: 10. Mai 2010 10:45

Beitrag von Sebastian Barth »

Schön, dass du mit mir darüber diskutierst.

Ich habe nicht sehr viel mit Threads gerarbeitet, dass stimmt, habe sie aber in Java, sowie c++ auch OS nah "ausprobiert" und bin daher sehr froh, dass Qt soviel für mich übernimmt.

QThread bietet schon jetzt eine Signal/Slot Verbindung zu quit(). Zudem kennt QThread alle QObjekte, welche zu ihm mit moveToThread() gemoved wurden. Also ist fast alles, was ich will, möglich:

QThread destructor:
1. QThread muss in seinem destructor als Allererstes lediglich den quit() slot aufrufen (per signal) und mit this.wait() darauf warten, dass sein thread beendet ist.

2. Danach kann er mit gutem Gewissen alle die QObjecte löschen, die zu ihm "gemoved" wurden und KEINEN parent besitzen.

3. Ist dies getan, kann die restliche Arbeit des Destruktors fortgesetzt werden.

Das Wichtigste daran ist, das der QThread selber darauf achtet, seinen von ihm gestarten thread zu beenden und sicher zu stellen, dass dieser beendet wurde, bevor er sich selbst zerstört. Das ist seine Aufgabe, nicht die des QThread erstellenden Scopes.

Ich habe mir die Klassen grob angeguckt und ich glaube nicht, dass das ein Problem wäre.

Die Vorraussetzung dafür ist, dass man definiert, dass QObjecte, die zu einem QThread mit moveToThread() "zugehörig" gemacht wurden, diesem auch gehören, er also Owner dieser gilt und diese löschen darf.

Das finde ich absolut sinnvoll und scheint mir problemlos möglich! Sogar als Subclass, um es als weiter QThread-Art zu etablieren.

Oder irre ich mich da?

Der Grund, denke ich, weshalb so etwas bisher noch nicht gemacht hat ist, dass niemand wirklich wusste, was der richtige Umgang mit QThreads seit 4.4 ist. Da selbst die doku es anders wollte, gab es keinen Bedarf.


Gruß,
Sebastian
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Ok, disskutieren wir bissi :-)
1. QThread muss in seinem destructor als Allererstes lediglich den quit() slot aufrufen (per signal) und mit this.wait() darauf warten, dass sein thread beendet ist.
der quit macht nur sinn, wenn der Thread in ner eventloop laeuft. also im run muss nen exec() aufgerufen werden.
Ich behaupt mal, nen grossteil aller Programme die QThread verwenden, werden keine eigene Eventloop haben ! Grossteil definier ich mal mit >60% ! oder lehn ich mich damit zu weit ausm fenster ?

Von denen die keine QT-Eventloop nutzen, denk ich mal, laufen paar einfach durch , und melden mit finished() dem erzeugenden thread das ... das wars.

Der Rest, denk ich mal iss auch ne Menge(um ned zu sagen eher der groesste Teil, weil threads die einfach auslaufen schlecht von aussen zu synchronisieren sind, d.h. die schlecht von user schlecht zu beenden sind), wird einfach traditionell in ner eigenen schleife laufen und ueber synchronisationsvariablen gesteuert. Funktioniert equivalent zu der eventloop, nur ist diese loop wahrscheinlich performanter, weil sie den QT ballast ned traegt, leute die ohne QT oder aehnliche Bibs Threads programmieren, kennen das eh ned anders, und leider eben das der Thread nicht durch quit() einfach zum anhalten gezwungen wird, sondern eben durch irgend was anderes .... das muesstest du dem DTor beibringen ! WIe wuerdest das machen ?

Also dein Vorgehen gaenge scho mal mit nem grossteil der Threads ned.

weiterhin:
Dtoren sind von natur aus sensibel :-)
gibt ne ganze menge an literatur, warum dtoren keine exceptions werfen sollten ^^
und so nen sensibles ding willst in ne warteschleife schicken ?

was machst du bei, sagen wir etwas widerspenstigeren threads ... also threads die sich nicht so ohne weiteres beenden lassen, weil sie haengen z.b.
Willst den User das programm um die ohren fliegen lassem, ala wenn der thread ned geht, kann ich nix fuer :-)
Grad wenn mit APIs zu spezieller hardware zu tun hasst, passiert das oefters ohne das was dazu kannst.

Die hohe Schule iss dann, nen thread mehrstufig zu beenden, ohne das dein Programm bockt ... aeh blockt.

Unter QT geht das recht human z.b. so:
du rufst das quit auf, oder setzt deine variable etc, also das, was deinen thread zum "aufgeben" bewegt.
Dann setzt dir nen timer .... und kehrst in die loop zurueck.(die App kann wieder nuetzliche aufgaben tun)
wenn dein thread sich normal beendet, sowas soll ja vorkommen,dann schickt er das finished. das holt deinen mainthread aus der eventloop, du setzt ne statusvariable, und gehst wieder in die loop zurueck. (jaja, damit die App weiter auf den user reagieren kann).

irgendwann iss dein timer faellig, du fliegst deswegen aus der loop.
du checkst die statusvariable, ob dein thread sich ned scho beendet hat, wenn ja iss alles ok, du kannst die ressourcen zum thread und alles rundherum loeschen.
Das ist der Normale weg, braucht zwar alles bissi (bis timer abgelaufen) aber die App hat in der Zeit nie geblockt. 99,9% der User sind zufrieden.

dann die unnormale variante.
Dein thread bockt, ergo nach dem timer hat das ding sich selber noch ned beendet. Du hasst allen grund ungeduldig zu sein !
Alternativ koenntest du hier noch paar sanftere methoden probieren, ... dem thread z.b. signale (keine QT signale, sondern OS Signale, ja auch das geht) schicken, wenn er denn drauf reagiert bzw dafuer programmiert ist ...
DU kannst auch gleich die harte variante waehlen.
Dein thread wird sich nie selber beenden, also zu besonderen mitteln greifen ! terminate (nein das schickst ned dem thread, sondern dem OS mit der threadId), dann nen wait mit nem timer triggern.
Richtig, hier blockiert deine GUI, wir haben aber auch ne aussnahmesituation.
laeuft der timer aus und ned das terminate, iss was ganz faul, deine chance das programm zu retten ist nahe NULL ... Hier waer es angebracht richtig boese Dinge zu tun (Core dump etc.)
zum glueck wird aber in den meisten faellen das terminate auslaufen, ergo dein thread iss zwar vom BS beendet worden, du hasst die ressourcen verloren, die der thread kontrollierte, die oxideiren nu im Speicher als Leichen rum (memory leaks), aber du kannst dein programm nach ner fehlermeldung (um den User angst einzujagen !) noch sauber beenden und ggf. Daten speichern.
Ergo von den 0,1% Usern sind vielleicht 90% dir ueberdankbar, das du trotz kritischer Fehlfunktion Ihre daten noch retten konnstest :-)

Und sowas woelltest du alles in den DTor packen ???

Es gibt uebrigens noch paar Coding styles die zu beachten waeren.

deshalbdie Frage:

Warum willst du Bitte im Thread Instanzen dynamisch erzeugen, die nur fuer den Thread gelten (also so lange wie der leauft), und dann durch die Verwaltung mit dem Thread zusammen beenden.

nen thread iss nichts anderes wie eine Funktion, die parellel zu den anderen threads ausgefuehrt wird.
also es laeuft nur die run() methode durch.
Selbst wenn du ein exec() drinne aufrufst, aendert sich nix drann, nur das das ding da drinne in ner schleife haengt.
Also wenn du dem dem das quit schickst, kommt er aus dem exec() und laeuft bis zum ende der run durch.

Das heisst du kannst ohne weiteres instanzen vor dem exec anlegen, und die dann nach dem exec() wieder zu loeschen.
Wenn die signale empfangen sollen, muessen die QObjects sein, richtig.
Aber sie muessen um himmels willen nicht dynamisch angelegt sein ...

C++ grundRegel fuer den performance-bewussten programmierer:
auf den Stack was geht ! in den Heap/Freestore was muss !
oder anders, meide new (und malloc,alloc ... ham sowieso nix in c++ zu suchen)!

also du kannst in deinem Run durchauss sowas machen:

Code: Alles auswählen

QMyThread::run() 
{
      QMySignalReceiverIrgendwas sigreceiver;
      connect(this,SIGNAL(terminated(),&sigreceiver,SLOT(makeBlueScreen()));

    exec(); 

    /// da sigreceiver lokal aufm Stack war, wird es hier automatisch ungueltig, die connections werden soweiso getrennt durch den DTOR ... alles super in ordnung. 

}
Und Objecte die ueber die laufzeit des Threads hinaus brauchst, solltest sowieso an ner ganz anderen stelle erzeugen !

Ciao ....
Sebastian Barth
Beiträge: 9
Registriert: 10. Mai 2010 10:45

Beitrag von Sebastian Barth »

Desktruktor:
Ich weiß leider nicht, wie kritisch die Arbeit im Destruktor ist. Vielleicht könntest du das etwas genauer erklären.

So, oder so, müssen wir, da wir ein weit übergeordnetes Objekt mit delete zerstören könnten, in irgendeinem Dekonstruktor doch darauf warten, dass der Thread beendet wird, sonst würden die Worker-Objekte doch nie gelöscht. Irgendwo müssen wir sie doch löschen, aber wo und wo wartet man auf das Terminieren der Threads, wenn nicht in einem Dekonstruktor, oder einer von ihm gestarteten Funktion?


QThread als Verantworlicher für den gestarteten Thread:
Ehrlich gesagt verstehe ich auch nicht so ganz, warum du die Leute, die mit ihren Klassen von QThread erben mit denen, die nur ein QThread erstellen in einen Topf wirfst. Diejenigen, die letzteres tun, müssen eh selber darauf achten, was passiert, falls ihre eigene Schleife (also ohne event loop) stoppen und der Thread terminieren soll. Alle Anderen müssen sich nicht um das Deleten kümmern, weil das der Thread auf eigenem und sicheren weg macht. Dabei haben sie nur die Verantwortung über die Slots, die von der Eventloop des QThreads gestartet wurden und müssen sicherstellen, dass diese auch irgendwann beenden. Genau das, müssen sie aber auch auf dem Hauptthread bei jedem anderen Slot beachten.

Wer also bleibt übrig?


Danke für dein Interesse!

Sebastian
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Im Wesentlichen stimme ich mit RHBaum überein (DTor schlecht geeignet, terminate() gefährlich usw..). Weil er (RHBaum) IMHO zuwenig auf die vorgeschlagene Variante (keine Ableitung von QThread) eingeht doch noch ein paar Hinweise auf die Ursprungsfrage(n):

Im Artikel wird aus OOP-Sicht argumentiert. Die Aussage "eine Berechnung IST ein Thread" ist aus OOP-Sicht kritisch... besser ist "eine Berechnung gehört zu einem Threadkontext".
Aus dieser Sicht kann man auch argumentieren: Der Threadkontext wird nicht mehr benötigt, wenn die Arbeit erledigt ist. Damit sähe z.B. die Setup-Methode wie folgt aus:

Code: Alles auswählen

void MyWidget::prepareWork()
{
  QThread *context = new QThread();
  mWorker = new Worker();
  mWorker->moveToThread(context);

  // Beispiel: auf Button-Klick beginnt die Action:
  connect(aPushButton, SIGNAL(clicked()), 
              mWorker, SLOT(startWork()));

  // Den Thread brauchen wir nach getaner Arbeit nicht mehr:
  connect(mWorker, SIGNAL(workingDone()),
              context, SLOT(quit()));

  // Context soll nach Beendigung Selbstmord begehen:
  connect(context, SIGNAL(finished()), context, SLOT(deleteLater()));

 // das waers.. start:
 context->start();
}
Da fehlt selbstverständlich noch ein klein wenig was.. aber das soll ja auch nur das Prinzip zeigen. Sollte die Berechnung mehrmals gestartet werden, könnte man den zweiten Connect auch einfach weglassen oder z.B. auf das "destroyed()"-Signal vom Worker reagieren. Dann würde sich der QThread selbst beenden und löschen, sobald in der GUI sowas in der Art aufgerufen wird:

Code: Alles auswählen

 if (!mWorker->isRunning()) {
    mWorker->deleteLater();
    mWorker = NULL;
 }
Also: Der Kontext (QThread) wird nun automatisch gelöscht, wenn er nicht weiter benötigt wird. Daher muss man sich beim Shutdown nur noch um eine Instanz kümmern.

Ich gebe RHBaum schon recht, dass es "Problemfälle" gibt, aber wir sind hier immernoch in einem Qt-Forum. Ich gehe nicht von der übelsten Anwendung aus, sondern von einer "Qt-homogenen" (Berechnung, Netzwerk, File-I/O oder sonst was).

Wann:
Kommt auf die Applikation drauf an... entweder wird der Anwender ungeduldig und klickt auf einen "Abbrechen"-Button.. der richtige Ort ist dann der entsprechene Slot. Oder jemand klickt das Widget zu. In diesem Fall ist es "closeEvent(...)". Da kann man dann super mit dem Benutzer interagieren (so im Stil: "Arbeit läuft, abbrechen?" "ok.. bitte warten"... "Arbeit läuft noch immer.. weiter warten?" .. usw.)

Wie:
Kommt auf die Applikation drauf an :wink: .. Meist verwende ich einen geblockten Mutex, welcher von der Workerinstanz freigegeben wird, sobal die Arbeit erledigt wurde. Da kann dann z.B. auch mit "tryLock()" gearbeitet werden, so dass die GUI nicht einfriert. Aber im Prinzip:

Code: Alles auswählen

void MyWidget::closeEvent(....)
{
  if (mWorker) {
    .. 
    mWorker->shutdown();
    .. // diverse Varianten denkbar mit mehrmaligen Nachfragen beim Benutzer oder Abbruch des Close-Events..
  }
}

void Worker::shutdown()
{
  // setze ein Flag oder sende ein Qt-Signal
  mMutex.lock(); // warten auf Beenden...
  // viele Varianten denkbar mit tryLock und Timeout und bool-Returnwert.
}
[EDIT]
Ich kann ausserdem nicht genau nachvollziehen, was genau Barth an der "richtigen" Variante stört.. denn in der "falschen":

Code: Alles auswählen

this->workerThread = new WorkerThread(this); 
musste er sich ja auch um den Shutdown kümmern.. also brauchte es im DTor sowieso noch Code (Autodeletion durch QObject-Verkettung hin oder her).

hth..
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Beitrag von RHBaum »

Ich gehe nicht von der übelsten Anwendung aus
Oft kannst du nich mal was dafuer ...
Oft nimmt man ja auch multithreading, wenn man einen langwierigen vorgang, von mir aus ne Berechnung, ned in viele kleine einzelne teilstuecke zerhacken kann.
Unter ner singlethreadanwendung wuerde die App stehen ...

Bei multithreaded bietest den user ja grad an, die tasten noch druecken zu koennen, obwohl die App noch voll zu tun hat.
Wenn der user dann den Schliessen button z.b. drueckt, hasst nur noch die Wahl:
die App gewinnt, der user muss sich gedulden bis das fenster zugeht, bzw die app aus der Prozessliste verschwindet !
oder der user gewinnt, und du musst den thread terminieren, mit allen konsequenzen.

Ciao ...
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

RHBaum hat geschrieben: oder der user gewinnt, und du musst den thread terminieren, mit allen konsequenzen.
Ja das stimmt.. es gibt Anwendungen wo schwierig sind in der Aufteilung (Terminierungspunkte). Aber dann bin ich jeweils knallhart: ein Terminieren eines Threads gibt's nicht.. Je nach Applikation entscheide ich mich dann für: a) der Anwender kann nicht abbrechen und muss warten, oder b) ich nehme kein Multithread sondern Multiprozess-Konzept mit IPC... ein Prozess kann dann problemlos gekillt werden :wink:
Zuletzt geändert von solarix am 5. Juli 2010 12:42, insgesamt 1-mal geändert.
Sebastian Barth
Beiträge: 9
Registriert: 10. Mai 2010 10:45

Beitrag von Sebastian Barth »

Schön, dass du dich beteiligst solarix.

Ich konnte leider nicht heraus bekommen, warum der Destruktor dafür nicht geeignet sein soll. Aber wenn das so ist, ok.

Dein Beispiel gefällt mir als aktuell schönste Lösung des Problems.
Eines hast du jedoch vergessen: Wer und wann deleted das Workerobjekt? Es hat ja kein QObject parent.

Muss man das mit delete machen? Es wäre schön, wenn das im Qt Stil mit deleteLater() oder mithilfe des parent/child systems machbar ist. Allerdings hat das Workerobjekt weder parent, noch läuft die Event Loop seines Thread. Ich hoffe, wir finden hier noch etwas Schöneres!

Was mich an deinem Weg insgesammt stört, ist dass, wie ich finde, eigentlich das QThread-Objekt das saubere Zerstören der Objekte vollbringen sollte, da es das Objekt ist, was Kontrolle über den Thread hällt und so am besten weiß, wann dieser Läuft und wann ein Zerstören der ihm zugeordneten Objekte erlaubt ist, oder nicht. Leider gefällt euch mein Vorschlag mit dem Warten im Destruktor nicht (warum auch immer).

EDIT:
Ich stimmte solarix übrigens zu: Ein Terminieren eines Threads ist nicht erlaubt. Dennoch ist es ratsam eine Notfallroutine einzubauen, die unsichere Daten speichert. Der User jedoch sollte dies nicht verursachen können. Ihn stört es sowieso nicht, wenn die Anwendung wie gewünscht vom Bildschirm verschwindet, im Hintergrund jedoch zum sicheren Terminieren weiterläuft und schließlich selbst und korrekt beendet.

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

Beitrag von solarix »

Sebastian Barth hat geschrieben: Eines hast du jedoch vergessen: Wer und wann deleted das Workerobjekt? Es hat ja kein QObject parent.
Naja.. also eigentlich ist es ja egal, ob ich einen Thread nur runterfahren oder runterfahren+löschen muss.. nach "mWorker->shutdown()" noch ein "delete mWorker" ist ja keine grosse Sache.

Allerdings frage ich mich, ob man nicht auch mit "setParent()" arbeiten könnte. Also angenommen, man hätte ein Signal "workingDone()" vom der Workerklasse. Dann könnte man doch wie folgt die ganze Kette automatisch Löschen:

Code: Alles auswählen

   QThread *context = new QThread();
    mWorker = new Worker();
    mWorker->moveToThread(context);

    // Wenn der Kontext gelöscht wird, soll auch der Worker gelöscht werden:
    mWorker->setParent(context);

   // Den Thread-Kontext brauchen wir nach getaner Arbeit nicht mehr:
   connect(mWorker, SIGNAL(workingDone()),
              context, SLOT(quit()));

   // Context soll nach Beendigung Selbstmord begehen:
   connect(context, SIGNAL(finished()), context, SLOT(deleteLater()));

   // weitere Signal-Connections..

  // los gehts
   context->start(); 
Allerdings habe ich ehrlich gesagt nicht ausprobiert, ob "setParent()" mit "moveToThread()" wie erhofft funktionieren würde..
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Ein kommentarloses "QtConcurrent::run()".
Keine Ahnung ob es in eure Vorlieben und Absichten passt ;)
Sebastian Barth
Beiträge: 9
Registriert: 10. Mai 2010 10:45

Beitrag von Sebastian Barth »

Das geht leider nicht, da der QThread context dem Main-Thread zugeordnet ist und das Worker-Objekt worker dem von ihm erstellten Thread. QObject Parents müssen dem selben Thread zugeordnet sein, wie ihre Kinder.

Genau das gehört ja auch zu dem, was ich bemängelt habe. Meine vorgeschlagene Lösung wäre, dass QThreads die QObjects, die ihm mittels QObject::moveToThread() zugeordnet wurden, auch als Kinder hinzufügen und löschen darf. Das wäre auch deshalb ok, da die Kinder von dessen gestarteten Thread behandelt werden und ohne diesem keinerlei Aufruf erhalten. Ob man das nutzt, wäre jedem selbst überlassen, QObject::setParent() muss nicht aufgerufen werden.

Ein delete worker; möchte ich sehr gerne vermeiden. Ich nutze nicht ohne Grund das Parent/Child-System von Qt. Vielleicht fällt uns noch etwas Anderes ein!

@franzf:
Ich wüsste nicht, wie QtConcurrent::run() uns da helfen könnte. Danke trotzdem. Vielleicht haben die anderen eine Idee.
Antworten