Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Thread für einfache Funktionsauslagerungen (https://www.delphipraxis.net/168952-thread-fuer-einfache-funktionsauslagerungen.html)

ibp 20. Jun 2012 11:20

Thread für einfache Funktionsauslagerungen
 
Hallo,

beim Umstellen eines Programms von Methoden auf Stored Procedures soll das ganze in einen Thread gepackt werden. Die SP sind komplex und zeitaufwendig, bisher wurden diese von der Mainform übernommen. Dabei gab es dann ein Infofenster mit einer Pseudoprogressbar die signalisiert das etwas in Arbeit ist. Bei der Umstellung auf SP friert nun dieses Infofenster ein. Lösung: Auslagerung in Thread.

Ist der nachfolgende Ansatz so richtig oder was ist noch zu beachten. Geht er vielleicht einfacher?

Danke schon mal....

Delphi-Quellcode:
  TMachWasLangesInDerDBThread = class(TThread)
    private
      fSPQuery : TIBQuery;
      fInProgress : Boolean;
    protected
      procedure Execute(); override;
    public
      constructor create(const aPKey,aTKey:TKey; aChildLevel:Integer);
      destructor Destroy; override;

      property InProgress : Boolean read fInProgress;
  end;

constructor TMachWasLangesInDerDBThread.create(const aPKey,aTKey:TKey; aChildLevel:Integer);
begin
  inherited create(False);
  FreeOnTerminate := False;

  fInProgress := True;

  fSPQuery := TIBQuery.Create(nil);
  try
    fSPQuery.SQL.Text:='EXECUTE PROCEDURE P_SORT...';
    fSPQuery.ParamByName(...);
  except
    on e: exception do
    begin
      showmessage(e.Message);
    end;
  end;
end;

destructor TMachWasLangesInDerDBThread.Destroy;
begin
  if Assigned(fSPQuery) then
  begin
    fSPQuery.Free;
  end;

  inherited destroy;
end;

procedure TMachWasLangesInDerDBThread.Execute;
begin
  try
    fSPQuery.ExecSQL;
  except
    on e: exception do
    begin
      showmessage(e.Message);
    end;
  end;
  fInProgress := False;
end;

.... im Hauptprozess Aufruf durch...

...
MeinInfoFenster.show;

aMYThread := TMachWasLangesInDerDBThread.create(...);
try
  while aMYThread.InProgress do
  begin
    MeinInfoFenster.Action;
    Sleep(1000);
  end;
finally
  aMYThread.Free;
end;
...

Bernhard Geyer 20. Jun 2012 11:22

AW: Thread für einfache Funktionsauslagerungen
 
Ist die verwendete DB-Komponente Thread-Save?
D.h. darfst du von unterschiedlichen Query-Komponenten auf eine einzige Connection-KOmponente zugreifen?
Falls nicht musst du in jedem Thread eine eigene DB-Verbindung aufbauen.

ibp 20. Jun 2012 11:30

AW: Thread für einfache Funktionsauslagerungen
 
das wäre nicht das Problem, ich kann ja die Entsprechenden Infos aus dem Hauptthread übergeben.

Was mir mehr sorgen macht, ist...
Delphi-Quellcode:
while aMYThread.InProgress do
begin
  MeinInfoFenster.Action;
  Sleep(1000);
end;
was ist wenn das durch ein Fehler zu einer Endlosschleife wird? Wie kann ich das vielleicht anders lösen?

p80286 20. Jun 2012 11:54

AW: Thread für einfache Funktionsauslagerungen
 
Wie wäre es ohne sleep aber mit Triggerung durch einen Timer?
oder so:
Delphi-Quellcode:
while aMYThread.InProgress do
begin
  aMyThread.InProgres:=false; {ich hab das read gesehen.....}
  MeinInfoFenster.Action;
  Sleep(1000);
end;
Gruß
K-H

CCRDude 20. Jun 2012 12:02

AW: Thread für einfache Funktionsauslagerungen
 
Warum kann Sleep nicht endlich aus der RTL rausgeworfen werden? :(

Deutlich besser ist WaitForSingleObject, dann lässt sich die "Wartepause" auch abbrechen (sonst z.B. nervig wenn bei Programmende einige Threads noch die eine oder andere Sekunde schlafen, beim System runterfahren, wenn man einfach nur abbrechen will...). Zum Beispiel mit einem Event (auf den man damit wartet).

NickelM 20. Jun 2012 12:10

AW: Thread für einfache Funktionsauslagerungen
 
Lass das mit Sleep und so weg, und mach eine Synchronize-Funktion am Ende das Thread, wo du
Delphi-Quellcode:
fInProgress := False;
hast. Damit sind beide Threads (Haupthread, also dein Fenster, und dein "Arbeiterthread") unabhängig von einander. Das mit
Delphi-Quellcode:
ShowMessage
im except würd ih ändern. Dass könnte unter umständen mal knallen. Verwende lieber MessageBox von Windows. Ist sicherer und bietet mehr :-D

Nachtrag, wegen anderen Posts : Oder mit WaitForSingleObject, wie schon erwähnt :-D. Ich persönlich find es blöd, da die Hauptanwendung im Endeffekt wieder einfriert.

Gruß NickelM

sirius 20. Jun 2012 12:12

AW: Thread für einfache Funktionsauslagerungen
 
Wird das Query wirklich im Thread ausgeführt. Denn wenn die Komponente in TThread.create erstellt wird, gehört sie zumindest noch dem Mainthread (müsste man überprüfen, wie die Komponente arbeitet.

Ansonsten: sleep vermeiden, steht ja schon da.


Edit:
Zitat:

Zitat von NickelM (Beitrag 1171739)
Ich persönlich find es blöd, da die Hauptanwendung im Endeffekt wieder einfriert.

Sieht ja so aus, als ob das notwendig ist. Stichwort Splashscreen und Vorarbeiten beim Start.

ChrisE 20. Jun 2012 13:11

AW: Thread für einfache Funktionsauslagerungen
 
Hi ibp,

also so wie ich das sehe, würde ich im Thread auf jedenfall eine eigene Connection aufbauen. Außerdem sei beachtet, dass der Constructor des Threads im Kontext des aufrufenden Threads - also nach Deinem Beispiel wahrscheinlich des Hauptthreads - ausgeführt wird. Ich würde daher alle Speichersachen erst am Anfang von Execute erzeugen/anlegen und auch am ende von Execute freigeben.
[EDIT]Wurde ja schon so von sirius geschrieben :-([/EDIT]

Dann der Aufruf. Es gibt da eben zwei Möglichkeiten - die eine ist nach meiner Ansicht der lineare Ansatz mit warten auf Beendigung wie du es gemacht hast. Sleep ist dafür nach meiner Meinung schlecht ich würde eher aktives Warten machen -> mit z.B. sowas.

Der andere ist der Ereignisgesteuerte. D.h. zerteilen von start und ende. Es gibt eine Methode die den Start steuert und nach dem Start der Threads die Oberfläche in einen "Bitte warten Zustand" bringt und damit endet. Dem Thread wird noch eine Methode übergeben für Beenden - vielleicht mit einer Aussage wie erfolgreich das war was er tun soll. In dieser Methode wird dann die Bedienung der Oberfläche wieder frei gegeben z.B. Statusbar wird wieder geschlossen - und alles andere was noch so gemacht werden muss, nachdem die Aufgabe des Threads erledigt ist.

Gruß, Chris

[EDIT]
Endlosschleife würde ich über das OnTerminate-Event des Threads verhindern mit einer lokalen Variable in der Anwendung. Diese Variable wird bei OnTerminate gesetzt und neben InProgress mit abgefragt
Delphi-Quellcode:
while MyThread.InProgress and l_IsRunning do
[/EDIT]

ibp 20. Jun 2012 13:23

AW: Thread für einfache Funktionsauslagerungen
 
also das Sleep war nur für das Infofenster, weil das so gerannt ist, das würde entsprechend noch angepasst werden und damit fliegt das Sleep wieder raus.

Delphi-Quellcode:
MeinInfoFenster.Action;
aktualisiert eigentlich nur nach einer bestimmten Anzahl von Aufrufen das Info-Fenster.

Showmessage steht da nur zu Testzwecken!

Brauche ich den ein Synchronize wenn Thread.Exceute abgearbeitet wurde?

ChrisE 20. Jun 2012 13:27

AW: Thread für einfache Funktionsauslagerungen
 
Zitat:

Zitat von ibp (Beitrag 1171760)
Brauche ich den ein Synchronize wenn Thread.Exceute abgearbeitet wurde?

Wie meinst du das? Wenn du im Execute des Threads etwas ausführst, dass etwas "verändert" was z.B. dem Hauptthread gehört, sollte das mit Syncronized aufgerufen werden - ja. Oder hab ich das jetzt falsch verstanden?

Gruß, Chris

ibp 20. Jun 2012 13:35

AW: Thread für einfache Funktionsauslagerungen
 
ja das tut der Thread aber nicht, das einzige was ich brauche ist der Zugriff auf den Status von fInProgress.

ChrisE 20. Jun 2012 13:38

AW: Thread für einfache Funktionsauslagerungen
 
Irgendwie stehe ich auf dem Schlauch. Du Fragst doch bereits über die Property des Threads diese Variable im Hauptthread ab. Sorry, ich versteh die Frage an sich nicht :-(

ibp 20. Jun 2012 13:45

AW: Thread für einfache Funktionsauslagerungen
 
ich frage das Property während des ganzen Prozesses ab, während die DB-SP läuft, damit ich dann so lange das Infofenster updaten kann.

Die fRage ist ja wenn ich im Excute Teil sich das Property ändert, brauche ich dann ein Synchronize, damit ich im Hauptthread die Änderung des Propertys mitbekomme.

ChrisE 20. Jun 2012 13:56

AW: Thread für einfache Funktionsauslagerungen
 
aus meiner Sicht musst du das nicht. Ich lasse mich aber gerne belehren.
Syncronize sorgt nur dafür, dass der Code im Kontext des MainThreads ausgeführt wird. Da du aber in Execute nur eine Variable auf False setzt und diese per Property im MainThread immer wieder abgefragt wird sehe ich da eigentlich kein Problem. Es greifen zwei Threads auf einen Speicherbereich zu, aber der eine eben nur lesend (durch Property sicher gestellt).

Wenn du auf Nummer sicher gehen willst, kannst du für die Variable Getter und Setter schreiben, die die Variable per CriticalSection absichern.

Gruß, Chris

NickelM 20. Jun 2012 17:22

AW: Thread für einfache Funktionsauslagerungen
 
@ChrisE : Er meinte, wie ich das mit meinem Post bezüglich Synchronize meinte.
@ibp : Okay hab mich zugegeben bischen doff ausgedrückt. Ich meinte, dass du das alles ein bischen anders aufbauen kannst, und somit kein Sleep oder sonstiges mehr brauchst.

Delphi-Quellcode:
 
TMachWasLangesInDerDBThread = class(TThread)
    private
      ...
      FErrorMsg : String; //Da man keine Funktion mit Parameter synchronizieren kann
      procedure OnErrorDo;
      procedure OnFinishDo;
    protected
      procedure Execute; override;
    public
   ...
  end;

procedure TMachWasLangesInDerDBThread.Execute;
begin
  try
    fSPQuery.ExecSQL;
  except
    on e: exception do
    begin
      FErrorMsg := e.Message;
      Synchronize(OnErrorDo);
      Terminate; //Zur Sicherheit
    end;
  end;
  Synchronize(OnFinishDo);
  Terminate; //Zur Sicherheit
end;

procedure TMachWasLangesInDerDBThread.OnErrorDo;
begin
  ShowMessage(FErrorMsg);
  //OnFinishDo; oder was du beim Error sonst nach alles gemacht hättest.
end;

procedure TMachWasLangesInDerDBThread.OnFinishDo;
begin
  //z.b. MeinInfoFenster.hide; oder was du nach deiner Schleife gemacht hättest
end;

.... im Hauptprozess Aufruf durch...

...
MeinInfoFenster.show;
aMYThread := TMachWasLangesInDerDBThread.create(...);
aMYThread.FreeOnTerminate := True;
So habe ich dass immer gelöst, wenn ich auf einen Thread warten musste. Damit bleibt das Fenster, wenn überhaupt, nur beim Synchronize-Teil kurz hängen, da beide Thread ja zusammen arbeiten.

Gruß NickelM

Furtbichler 20. Jun 2012 18:48

AW: Thread für einfache Funktionsauslagerungen
 
Ich verwende einen Connectionpool, der mir TIBDatabase-Componenten bereit hält.

Die Threads haben eine lokale im Thread erzeugte TIBStoredProc-Instanz. Beim Ausführen fordern sie eine Connection an, weisen sie der SP zu, führen sie aus und geben die Connection wieder an den Pool zurück.

Der Pool verwaltet nur eine TThreadList if TIBDatabase. Wenn eine Verbindung angefordert wird, aber die Liste leer ist, wird eine neue erstellt.

Keine Ahnung, ob das sinnig ist, aber ich habe es mal so umgesetzt und das läuft sehr gut. Ich verwende das für eine Datenpumpe, wobei die Threads als Workerthread fungieren.

Ich würde außerdem zusehen, das die SP nicht allzulange braucht. Wenn sie das tut, hast Du vermutlich etwas falsch gemacht.


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:14 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz