AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Projekte Workerthread: Der Diener im Hintergrund

Workerthread: Der Diener im Hintergrund

Ein Thema von alzaimar · begonnen am 12. Jun 2007 · letzter Beitrag vom 23. Jun 2011
Antwort Antwort
Seite 7 von 8   « Erste     567 8   
alzaimar
Registriert seit: 6. Mai 2005
Hallo,

Threads sind eine tolle Sache. Wenn man sie versteht. Wenn nicht, bringen sie einen an den Rand des Wahnsinns. Oder ein Stück weiter. Ich weiss, wovon ich rede.

Ich benötige für meine Applikationen immer mal wieder einen Thread, der ab und zu einzelne Jobs im Hintergrund ausführen muss. Wenn man z.B. auf bestimmte Ereignisse reagieren muss, diese Reaktion aber zeitaufwändig ist, dann kann die Ereignisbehandlung während der Ausführung des Eventhandlers blockiert sein.

Also habe ich einen generischen Thread implementiert, der einzelne Aufgaben (Jobs) im Hintergrund abarbeitet. Ich muss dazu nur den Job selbst definieren, alles Andere macht dieser Thread. Diese Threads nennen sich auch 'worker threads', weil sie eben ankommende Arbeiten ausführen.

So ein Job kann z.B. das Einlesen einer Tabelle von einer Datenbank sein, oder auch das Abspeichern von Daten.

Die Unit instantiiert einen Workerthread 'PendingJobs', den man sofort mit Arbeit befüllen kann. Dazu ruft man einfach die 'AddJob'-Methode auf:
Delphi-Quellcode:
...
PendingJob.AddJob (TMyJob.Create);
...
Die Anweisung schiebt den Job ans Ende der Jobliste und aktiviert ggf. den Thread (wenn der nicht sowieso schon aktiv ist). Der TMyJob wird dann 'demnächst' im Hintergrund ausgeführt.

Wenn man einen Job definiert, der mit COM-Objekten arbeitet (z.B. ADO) dann setzt man die Eigenschaft 'UsesCOMObjects' des Jobs auf True.


Der Thread verwendet eine Semaphore sowie eine Threadliste (die Liste der noch nicht ausgeführten Jobs). Auf Schnickschnack habe ich verzichtet, das kann jeder selbst einbauen.

Ich habe die Unit sowie eine kleine bescheuerte Demo bereitgestellt, die jedoch zeigt, wie einfach man Hintergrundarbeiten in seiner Anwendung implementieren kann.

Kritik, Verbesserungs- und Erweiterungsvorschläge sind ausdrücklich erwünscht.

Viel Spaß damit.

Update: Die Idee von thkerkmann wurde aufgenommen: Die Synchronize-Methode des Workerthreads ist nun public und kann innerhalb der TJob.Execute-Methode aufgrufen werden.

Update: Idee von shmia: Exception-Notification Event, und eine verbesserte Beendigung des Workerthreads. Man kann unn steuern, ob vor Beendigung alle ausstehenden Jobs noch abgearbeitet werden sollen oder nicht.

Update: Aus dem Thread wurde ein Threadpool (Idee von shmia). Er ist in einer wackeligen Version fertig. Er läuft in der Demo stabil, aber was heißt das schon. Ich würde Euch bitten, mal kräftig gegen die Klasse zu treten. Mal sehen, was sie aushält.

Der Threadpool verwaltet mehrere Threads (Anzahl einstellbar, auch zur Laufzeit), die gemeinsam die oben erwähnte Jobliste abarbeiten.

Update vom Threadpool: Schleifenvariablen als Cardinal deklariert, wenn Schleifenende = 'X-1', und X=0, bombt es.

Update vom Threadpool: Memoryleak beim Beenden beseitigt. Weiterhin stellt die Unit keine Instanz 'PendingJobs' mehr bereit. Denn das wurde im Finalisierungsabschnitt der Unit wieder freigegeben. Wenn die Classes-Unit ihren Finalisierungsabschnitt schon durchlaufen hat, gibt es Probleme.

Update vom Threadpool: Das Herabsetzen der Poolsize zur Laufzeit führte zu einem Speicherleck, ein fehlendes 'UnlockList' im der Clear-Methode der JobList und noch ein paar Kommentare.

Update vom Threadpool: x000x hat einen Bug gefunden: Die Setter-Methode des OnJobException-Events muss den Methodenzeiger natürlich an die einzelnen Threads weiterleiten. Weiterhin habe ich die Workerthread-Unit rausgenommen. Man braucht sie nicht mehr.

Update vom Threadpool: Die aktuelle Version läuft stabil, sie ist in einer Kundenanwendung im Einsatz.

Update vom Threadpool: Neue Methode, um aus einem Job heraus Nachrichten zu verschicken (siehe beiliegende Demo).

Update: Eigenschaft 'Terminated' des TWorkerThread wird publiziert, kleiner Fehler in der Clear-Methode beseitigt.

Update: Kleiner Hack, um den Threadnamen zu setzen (Debugginghilfe). Dank an Assertor.
Angehängte Dateien
Dateityp: rar workerthreadpool_198.rar (181,1 KB, 1170x aufgerufen)
"Wenn ist das Nunstruck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput!"
(Monty Python "Joke Warefare")
 
alzaimar

 
Delphi 2007 Enterprise
 
#61
  Alt 29. Nov 2008, 15:07
Hi Assertor,

Ich bau das mal ein und stelle es im 1.Post wieder rein.

Edit: Achso, äh.. Danke für das Kompliment.
  Mit Zitat antworten Zitat
Assertor

 
Turbo C++
 
#62
  Alt 29. Nov 2008, 17:15
Hi Alzaimar,

Zitat von alzaimar:
Edit: Achso, äh.. Danke für das Kompliment.
Bitte, gerne

Frederik
  Mit Zitat antworten Zitat
Assertor

 
Turbo C++
 
#63
  Alt 17. Mai 2009, 01:42
Hi,

noch eine kleine Verbesserung, um das Debugging in neueren Delphi Versionen zu vereinfachen:

Delphi-Quellcode:
--- D:/Downloads/csWorkerThreadPool.pas   Sat Nov 29 15:30:22 2008
+++ D:/csWorkerThreadPool.pas   Sun May 17 01:38:13 2009
@@ -113,6 +113,7 @@
     fMsg: String;
     Procedure DoAction;
 {$ENDIF}
+ procedure SetName;
     Procedure Run;
     Procedure DoOnException;
     procedure DoNotify;
@@ -265,6 +266,35 @@
 
 { TWorkerThread }
 
+{$IFDEF MSWINDOWS}
+type
+ TThreadNameInfo = record
+ FType: LongWord; // must be 0x1000
+ FName: PAnsiChar; // pointer to name (in user address space)
+ FThreadID: LongWord; // thread ID (-1 indicates caller thread)
+ FFlags: LongWord; // reserved for future use, must be zero
+ end;
+{$ENDIF}
+
+procedure TWorkerThread.SetName;
+{$IFDEF MSWINDOWS}
+var
+ ThreadNameInfo: TThreadNameInfo;
+{$ENDIF}
+begin
+{$IFDEF MSWINDOWS}
+ ThreadNameInfo.FType := $1000;
+ ThreadNameInfo.FName := 'csWorkerThread';
+ ThreadNameInfo.FThreadID := $FFFFFFFF;
+ ThreadNameInfo.FFlags := 0;
+ try
+ RaiseException($406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord),
+ @ThreadNameInfo);
+ except
+ end;
+{$ENDIF}
+end;
+
 Constructor TWorkerThread.Create(aID: Integer; aJobList: TWorkerThreadJobList);
 Begin
   Inherited Create(True);
@@ -302,6 +332,7 @@
 
 Procedure TWorkerThread.Execute;
 Begin
+ SetName;
   While Not Terminated Do Begin
     fCurrentJob := fJobList.WaitForNextJob;
     If Assigned(fCurrentJob) Then Begin
Das benennt die Threads dann ordentlich, so daß man diese im Debugger auch erkennt Ich weiß aus dem Kopf nicht, ob die Exception für benannte Threads < Delphi 2006 Probleme macht, vielleich packst Du noch ein IFDEF rein, damit es nur > Delphi 2006 verwendet wird.

Gruß Assertor
Frederik
  Mit Zitat antworten Zitat
alzaimar

 
Delphi 2007 Enterprise
 
#64
  Alt 17. Mai 2009, 10:15
Davon von den @@ Tags habe ich noch nie etwas gehört. Kannst Du mir kurz erklären, worum es dabei geht?
  Mit Zitat antworten Zitat
Win32.API
 
#65
  Alt 17. Mai 2009, 10:27
Das ist nur eine Darstellungsform von Unterschieden zwischen zwei Versionen.


Zitat:
++ -> Datei 1
-- -> Datei 2
+ -> Hinzugefügt
- -> Gelöscht
@@ -> Vieleicht Zeilen angaben?
Also alles, wo ein + vorsteht, ist neu und muss ergänzt werden.

--win32
  Mit Zitat antworten Zitat
alzaimar

 
Delphi 2007 Enterprise
 
#66
  Alt 17. Mai 2009, 11:06
Aha. Ich habe das mal eingebaut und geprüft. Delphi 6 meckert bei der Ausführung mit einer 'external Exception', läuft ansonsten durch. Ich habe das dann aber nur für Delphi 2006 und höher aktiviert.

Zur Ermittlung der Delphi-Version habe ich 'JEDI-SDL.INC' verwendet. Wenn es eine andere/bessere Möglichkeit gibt, die Delphi-Version zu ermitteln, immer her damit.

Update wie immer im 1.Post.
  Mit Zitat antworten Zitat
Assertor

 
Turbo C++
 
#67
  Alt 17. Mai 2009, 11:43
Hi,

@Win32.API: Danke, warst schneller mit dem Erklären

Zitat von alzaimar:
Aha. Ich habe das mal eingebaut und geprüft. Delphi 6 meckert bei der Ausführung mit einer 'external Exception', läuft ansonsten durch. Ich habe das dann aber nur für Delphi 2006 und höher aktiviert.

Zur Ermittlung der Delphi-Version habe ich 'JEDI-SDL.INC' verwendet. Wenn es eine andere/bessere Möglichkeit gibt, die Delphi-Version zu ermitteln, immer her damit.
Ich nutze immer gerne die Compilers.inc von Mike Lischke und Jim Kueneman. Das ist die Include für die VirtualTreeView. Link zum Source (steht unter der MIT Lizenz): http://new.lischke-online.de:8080/br.../Compilers.inc

Dann einfach im Source um alle Änderungen ein
{$IFDEF COMPILER_6_UP}...{$ENDIF} Das mit der Änderungsdatei war gestern einfacher, um die geänderten Stellen zu finden - war etwas faul Tools wie WinMerge (Bild) erstellen sowas und sind eine erhebliche Arbeitserleichterung.

Gruß Assertor
Frederik
  Mit Zitat antworten Zitat
alzaimar

 
Delphi 2007 Enterprise
 
#68
  Alt 17. Mai 2009, 12:00
Gut, diese JEDI-SDL.Inc macht im Prinzip das Gleiche...
  Mit Zitat antworten Zitat
Assertor

 
Turbo C++
 
#69
  Alt 17. Mai 2009, 12:07
Hi,

Zitat von alzaimar:
Gut, diese JEDI-SDL.Inc macht im Prinzip das Gleiche...
Ja, nur die Lizenz der Compilers.inc ist noch liberaler. Für Projekte von Entwicklern, die keine JEDIs einsetzen vielleicht besser.

Außerdem nimmt die JEDI-SDL.inc soweit ich sehen kann auch noch andere Änderungen an den Compiler Einstellungen vor (z.B. {$F+,Q-,R-,S-,I-,A+}). Das braucht der Diener im Hintegrund nicht und führt u.U. - wenn projektweit - zu Probleme oder ungewünschten Einstellungen.

Bei der Compilers.inc werden wirklich nur die Compiler festgestellt, kein Rumdrehen an Einstellungen.

Gruß Assertor
Frederik
  Mit Zitat antworten Zitat
Benutzerbild von sundance
sundance

 
Delphi 7 Professional
 
#70
  Alt 26. Aug 2009, 17:59
@alzaimar:
Erst mal vielen Dank für deine Mühe, die du in diese Unit gesteckt hast. Ich bin gerade dabei, in einem von meinen kleinen "Projekten" davon zu profitieren....

Ein paar Fragen/Anmerkungen hätte ich aber doch:

- Was bezweckst du mit der Methode TWorkerThreadPool.Notify?

- Wie würdest du es anstellen, auf die Abarbeitung aller Jobs in der List zu warten, um z.B. irgendwelche Controls zu aktualisieren oder eine Statusmeldung auszugeben? (Polling auf TWorkerThreadPool.PendingJobCount = 0 ist ja wohl nicht der beste Weg...)

- In diesem Thread war mal eine Anmerkung:
Zitat:
Eins hab ich aber nicht ganz verstanden:
Wenn man Synchronized auf true setzt, wird der ganze Job über Synchronize(Run) ausgeführt. Wird das nicht zu einer blockierenden Operation dann ? Dann kann ich doch eigentlich diese Arbeit gleich im Haupt-VCL-thread ausführen lassen.
Zitat:
stimmt auffallend...
Nun ist das aber immer noch so drin:
Delphi-Quellcode:
If fCurrentJob.Synchronized Then
  Synchronize(Run)
Else
  Run;
Läuft der synchronisierte Thread nun autark oder nicht?
(na ja, vielleicht habe ich deine Source auch noch nicht ganz verstanden...)

.sundance.
  Mit Zitat antworten Zitat
Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:54 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