Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - TThread mit vererbung - wie _sauber_ umsetzen ?


funcry - Sa 06.12.08 19:05
Titel: TThread mit vererbung - wie _sauber_ umsetzen ?
Hallo zusammen,

ich arbeite derzeit an einem privaten Projekt, und verstehe nicht wie man _sauber_ folgende Struktur hinbekommen kann. das Problem ist wie folgt:

Es gibt eine Basisklasse:


Delphi-Quelltext
1:
Tmessage = class(TObject)                    


welche nichts anders tut als Meldungen auf ein im übergebenes TRichEdit auszugeben.

Davon abgeleitet gibt es eine Klasse:


Delphi-Quelltext
1:
rechnen = class(TMessage)                    


welche bestimmte Rechenoperationen durchführt.

Innerhalt der Klasse "rechnen" gibt es eine funktion:


Delphi-Quelltext
1:
function vielrechnen(param1, param2: double):Boolean;                    


welche eine sehr lange Berechnung durchführt.

Problem:
Meine Überlegung ist, dass ich gerne die Klasse "rechnen" mehrfach erstelle würde und die dort enthaltene Funktion "vielrechnen" jeweils in einem eigenen Thread ablaufen lassen würde. Das ganze liegt natürlich im Quelltext vor, welcher jedoch rund 1000 Zeilen hat, daher habe ich das Problem hier abstrahiert. Kurz gesagt, mein Problem ist, wie überführe ich mein Objekt "rechnen" sauber in einen Thread.

Getestet habe ich folgende Varianten, finde diese jedoch alle unsauber.

Variante 1:
Einführen eine Basisklasse:

Delphi-Quelltext
1:
Tsuperthread = class(TThread)                    

davon abgeleitet

Delphi-Quelltext
1:
Tmessage = class(Tsuperthread)                    

davon abgeleitet

Delphi-Quelltext
1:
Trechnen = class(Tmessage)                    


(Innerhalb von Tmessage kann ich execute von tthread überschreiben, und dort vielrechnen aufrufen)
Prinzipiell würde das klappen, nur wäre das nicht mit Kanonen auf Spatzen geschossen ? Ich will ja eigentlich nur die Funktion vielrechnen in "rechnen" in einem eigen Thread ausführen!

Variante 2:
Trechnen ist abgeleitet von TThread, und enthält als lokale Variabble:

Delphi-Quelltext
1:
var message: TMessage;                    


Würde auch funktionieren, wobei ich ungern auf die Verebung an dieser Stelle verzichten will.

Variante 3:
TRechnen bekommt als nested Objekt einen eigenen thread:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
trechnen = class(Tmessage)
type TmyThread = class(TThread)
[...]
end;
[...]
end;


Nur wie greife ich in TmyThread auf Funktionen von TRechnen zu ?

Irgendwie klappt das nicht.

Variante 4:
Ich erstelle eine Klasse:

Delphi-Quelltext
1:
ThreadedRechnen = class(TThread, TRechnen)                    


So sollte es eigentlich funktionieren, nur kann das Delphi nicht wie mir scheint.


Daher die Frage an Euch: Was ist eine saubere Lösung für das beschriebene Problem ?


jaenicke - Sa 06.12.08 19:20

Du könntest mit BeginThread [http://msdn.microsoft.com/en-us/library/ms682453(VS.85).aspx] einen Thread erzeugen, der nur aus der Funktion besteht. Die verlinkte Seite enthält Informationen zur durch diese Delphifunktion gekapselten API-Funktion.
Mehr dazu findest du u.a. hier:
http://www.delphi-treff.de/tutorials/objectpascal/threads/page/5/
und hier:
http://www.delphipraxis.net/post684623.html

Ansonsten kannst du auch Variante 3 benutzen. Der Thread müsste ggf. eine Referenz auf die anderen Klassen bekommen und Zugriffe darauf mit Synchronize synchronisieren, d.h. im Kontext des Hauptthreads ausführen, insbesondere bei VCL-Zugriffen gibt es sonst Probleme.


funcry - Sa 06.12.08 23:43

Danke für die prompte Antwort! Das ist die Lösung.

Innerhalb meiner Klasse "rechnen" habe ich folgende Methode eingebaut, welche als Thread die ursprüngliche Methode "vielrechnen" aufruft. Natürlich ist das nur das Grundgerüst (ohne Exceptions, Endthread etc. ...), aber der Weg scheint mir praktikabel - und einigermassen übersichtlich - zu sein :-)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure Trechnen.vielrechnen_asThread;
  function Test(P: Pointer): Integer;
  var
    My_rechnen: Trechnen;
  begin
    My_rechnen := p;
    My_rechnen.vielrechnen;  // Aufruf der tatsächlichen Funktion.
  end;

var
  ThreadID: LongWord;

begin
  BeginThread(nil0, Addr(Test), self, 0, ThreadID);
end;


funcry - Sa 13.12.08 10:41

Ich muss nochmal darauf zurückkommen. Zwischenzeitlich habe ich Variante 4 umgesetzt, weil ich über die Frage gestolpert bin, wann kann ich einen Thread wieder freigeben. Allerdings habe ich auch hier ekien Lösung gefunden, so dass ich jetzt wieder auf die hier vorgeschlagene Variante zurückgekommen bin.

Sowohl (!!!) bei variante 4 als auch hier verstehe ich eines nicht:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure Trechnen.vielrechnen_asThread;
  function Test(P: Pointer): Integer;
  var
    My_rechnen: Trechnen;
  begin
    My_rechnen := p;
    My_rechnen.vielrechnen;  // Aufruf der tatsächlichen Funktion.
  end;

var
  ThreadID: LongWord;

begin
  BeginThread(nil0, Addr(Test), self, 0, ThreadID);
end;


Dies habe ich letztlich in dies überführt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure Trechnen.vielrechnen_asThread;
  function Test(P: Pointer): Integer;
    self.vielrechnen(p);  // Aufruf der tatsächlichen Funktion.
  end;

var
  ThreadID: LongWord;

begin
  BeginThread(nil0, Addr(Test), self, 0, ThreadID);
end;


mit:


Delphi-Quelltext
1:
2:
3:
4:
5:
Trechnen.vielrechnen(sender: Trechnen);
begin
  self := sender;  // ?? warum ??
  [...]
end;


Die Zeile:


Delphi-Quelltext
1:
self := sender                    


ist dabei notwendig, da - so sieht es im Debugger aus - vielrechnen nicht auf Felder innerhalb des eigenen Objektes zugreifen kann. Spricht diese sind entweder NIL oder 0. Und das obwohl ich ja vielrechnen aus dem eigenen Objekt heraus aufgerufen habe. Genau die gleiche Problematik war auch bei Variante 4. Wobei - um die Verwirrung komplett zu machen - bei Methoden welche ich in vielrechnen aufrufe, welcher sich widerum auf Objektfelder des eigenen Objektes beziehen, dieses Problem nicht bestand.

Woran liegt das ? Und ist das wirklich sauber programmiert ?