Ich hab aktuell eine Laderoutine die hat eine globale Liste (Tasks) über die sie sich absichert.
Kann auch für z.B. die selbe Anzeige-Komponente, während einer läd was anderes laden und paralell dem ersten Sagen, dass seine Arbeit nicht mehr benötigt wird
und alles ohne Warten und somit auch ohne Deadlocks für den Benutzer.
Jeder Thread/Task der gestartet wird trägt sich in eine Liste ein, und den von dem er gestartet wurde, bzw. an wen er sich am Ende wieder wenden will.
Wer fertig ist, trägt sich aus der Liste wieder aus, bzw. wird automatisch ausgetragen (vor allem der Besitzer/Starter).
https://www.delphipraxis.net/203408-...ml#post1457779
Spätestens am Ende schauen die Threads dann nochmal nach, ob sie noch arbeiten sollen und wenn doch, erst dann wenden sie sich an an den Aufrufer. (wenn nicht, dann beenden und es ist egal, ob der Aufrufer noch existiert und der gespeicherte Zeiger/Callback schon ungültig ist)
PS: in den NextGen-Compilern (Android/iOS) verhalten sich Komponenten wie Interfaces, also di gibst die Form frei, aber da du noch einen Objektzeiger oder Callback (mit einem Objektzeiger drin) hast, existiert die Komponente dann immernoch
Ist zwar aktuell nur im
VCL/Windows im Einsatz, aber sollte überall laufen.
Delphi-Quellcode:
TBackgroundTasks.Start(Image1, TLoadImageThread.Create(Filename));
// oder
TBackgroundTasks.Start<string>(Image1, LoadImageProc, Filename);
procedure TIrgendwas.LoadImageProc(ID: TTaskID; Owner: TObject; Filename: string);
var
xxx: TBitmap;
begin
// laden
xxx.LoadFromUndMachIrgendwas(Filename);
TBackgroundTasks.Synchronize(procedure
begin
if not TBackgroundTasks.Check(ID) then // Aufgabe ist abgelaufen
Exit;
// anzeigen
Image1.Picture.Bitmap.Assigned(xxx);
end);
end;
Gibt im Prinzip nur 3 Funktionen,
* Start = Ausgabe starten
* Stop = Ausgabe für Beendet erklären
* Check = Prüfen ob Aufgabe noch läuft (von außen), bzw. ob sie sich beenden soll (von innen)
denen man Threads oder Prozeduren, Methoden oder Anonyme geben kann, die dann im Hintergrund werkeln und die sich selbst zentral organisieren,
wobei Stop automatisch ausgelöst wird, wenn der Owner verschwindet. (über das Syncronize am Ende wird sichergestellt, dass zwischen der Check-Abfrage und der Anzeige nicht doch noch der Owner verschindet, da das alles im Hauptthread läuft)
Ansonsten fängst du selber an über globale oder gegenseitige Kreutreferenzen und eine Threadsichere Synchronisierung
jeweils beim Gegenüber die Referenz auf sich selbst zu entfernen, bzw. dem Anderen zu sagen dass es nun vorbei ist. und mit etwas Glück schaffst du es dass jeder auf den Anderen wartet und es hängen bleibt.
Drum hatte ich mir diese asynchrone Unterhaltung gebaut, die nur ganz kurz beim Zugriff auf die Liste gesperrt wird.
Wem das mit dem "über das Syncronize am Ende wird sichergestellt" nicht gefällt, der muß da noch ein Lock/Unlock veröffentlichen, um die Liste für länger zu sperren (sperren, checken, machen, entsperren), aber als Prevention, dass so niemand einen Deadlock einbauen kann, hatte ich es garnicht erst eingebaut.
Also externer Zugriff auf die zentrale CriticalSection, bzw. diesem TMonitor.
(Achtung: System.TMonitor, nicht Forms.TMonitor ... k.A. wer auf die kranke Idee kam und diese Benaumng blind aus'm .NET geklaut hat)
Zitat:
Delphi-Quellcode:
// Start
TBackgroundTasks.Start(Self, ...);
// Stopp
TBackgroundTasks.Stop(Self);
// Restart
TBackgroundTasks.Stop(Self);
TBackgroundTasks.Start(Self, ...);
// läuft noch?
running := TBackgroundTasks.Check(Self);
// Stopp über ID
ID := TBackgroundTasks.Start(OnwerOrNil, ...);
...
TBackgroundTasks.Stop(ID);
// Stopp über Klassenmethode (gleiches bei TThread-Zeiger)
TBackgroundTasks.Start(OnwerOrNil, ThreadMethod);
...
TBackgroundTasks.Stop(ThreadMethod);
// Funktion starten
TBackgroundTasks.Stop(Self);
TBackgroundTasks.Start(Self, ThreadProcedur, ...);
// Thread starten (Create mit Suspended=True und im TBackgroundTasks.Start wird Thread.Start aufgerufen)
TBackgroundTasks.Stop(Self);
TBackgroundTasks.Start(Self, TBeispielThread.Create(...));
TBackgroundTasks.Stop(Self);
Thread := TBeispielThread.Create(...);
TBackgroundTasks.Start(Self, Thread);
// Parameter (Start)
TBackgroundTasks.Start(OwnerOrNil, Thread); // suspended erstellte TThread-Instanz
TBackgroundTasks.Start(OwnerOrNil, Thread, True); // manuell aus TBackgroundTasks entfernen, wenn Thread beendet (wenn Thread.OnTerminate<>nil)
TBackgroundTasks.Start(OwnerOrNil, Proc); // Klassenmethode, anonyme Methode oder Prozedur
TBackgroundTasks.Start<A>(OwnerOrNil, Proc, ParamA); // ... mit Parameter
TBackgroundTasks.Start<A,B>(OwnerOrNil, Proc, ParamA, ParamB);
TaskID := TBackgroundTasks.Start...; // ID für Check/Stop
// Parameter (Check und Stop)
TBackgroundTasks.StopAll; // ALLE Threads
TBackgroundTasks.Stop(Owner); // irgendwas am Owner (Threads oder Prozeduren)
TBackgroundTasks.Stop(TaskID); // bestimmte ID (Result der Start)
TBackgroundTasks.Stop(Thread); // bestimmter Thread
TBackgroundTasks.Stop(Method); // bestimmte Methode, auch mehrfach (nur Klassenmethoden, keine Prozeduren oder anonyme Methoden)
TBackgroundTasks.Stop<...>(Method); // ...
// Beispiel-Prozedur (Alternativen als Kommentar)
TBackgroundTasks.Start(Self, Beispiel, {ParamA, ParamB});
procedure TIrgendwas.BeispielProc(ID: TTaskID; Owner: TObject; {ParamA, ParamB: TIrgendwas});
begin
TThread.NameThreadForDebugging(AnsiString(Format('TBackgroundTasks:BeispielProc TaskID=%d', [TaskID]))); // falls nicht, wurde es vorher nur mit der TaskID bereits erledigt
{ berechnen }
...
if not TBackgroundTasks.Check(ID) then
Exit;
...
//TThread.Synchronize(nil, procedure
TBackgroundTasks.Synchronize(procedure
begin
if not TBackgroundTasks.Check(ID) then // Aufgabe ist abgelaufen
Exit;
{ anzeigen }
...
end);
//TBackgroundTasks.Synchronize(ID, SyncProc); //procedure {TIrgendwas.}SyncProc(ID: TTaskID; Owner: TObject; Value: Pointer);
//TBackgroundTasks.Synchronize(ID, SyncProc, Value);
end;
// Beispiel-Thread (Optionales und Alternativen als Kommentar)
TBackgroundTasks.Start(Self, TBeispielThread.Create{(ParamA, ParamB)});
type
TBeispielThread = class(TTaskThread) // es geht auch jede andere TThread-Klasse
private
//FParamA, FParamB: TIrgendwas;
procedure SyncProc;
protected
procedure Execute; override;
public
constructor Create{(ParamA, ParamB: TIrgendwas)};
destructor Destroy; override;
end;
procedure TBeispielThread.SyncProc;
begin
//if not TBackgroundTasks.Check(TaskID_or_Self) then
if not TaskCheck then // Aufgabe ist abgelaufen
Exit;
{ anzeigen }
...
end;
procedure TBeispielThread.Execute;
begin
inherited; // oder TThread.NameThreadForDebugging(AnsiString(Format('TBeispielThread ID=%d ...', [TaskID])));
{ berechnen }
...
//if not TBackgroundTasks.Check(TaskID_or_Self) then
if not TaskCheck then // Aufgabe ist abgelaufen
Exit;
...
Synchronize(SyncProc);
{Synchronize(procedure
begin
//if not TBackgroundTasks.Check(TaskID_or_Self) then
if not TaskCheck then // Aufgabe ist abgelaufen
Exit;
{ anzeigen }
...
end);}
end;
constructor TBeispielThread.Create{(ParamA, ParamB: TIrgendwas)}
begin
inherited Create;
//FParamA := ParamA;
//FParamB := ParamB:
end;
destructor TBeispielThread.Destroy;
begin
//ParamA.Free;
inherited;
end;
// mit TThread-Klasse, wo OnTerminate nicht überschreibbar ist (ICallStopOnTerminate=True)
TBackgroundTasks.Start(OwnerOrNil, TMyThread.Create, True);
procedure TMyThread.Execute;
begin
TThread.NameThreadForDebugging(AnsiString(Format('TMyThread ID=%d ...', [TaskID])));
try
...
finally
//TBackgroundTasks.Stop(TaskID_or_Self);
TaskStop; // Thread ist fertig (alternativ das TaskStop im Thread.OnTerminate aufrufen)
end;
end;