Schonmal vielen Dank für die Beteiligung!
Zunächst mal zu Sir Rufos Vorschlag:
Gefällt mir eigentlich sehr gut, nur ist der Haken, dass ich auf keinen Fall dem Benutzer zumuten möchte, unnötig auf den Thread zu warten, da es keinen Schaden anrichtet, wenn er bei "unkritischen" Arbeiten unterbrochen wird. Es gibt allerdings auch "kritische" Arbeiten, die er in jedem Fall entweder ganz oder garnicht durchführen soll. Dazu habe ich folgende Lösung erarbeitet:
MainForm:
Delphi-Quellcode:
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FThreadManager: TThreadmanager;
end;
implementation
procedure TMainForm.FormCreate(Sender: TObject);
begin
FThreadManager := TThreadmanager.Create;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FreeAndNil(FThreadManager);
end;
ThreadManager:
Delphi-Quellcode:
type
TThreadManager = class(TObject)
private
FMyThread: TMyThread;
public
destructor Destroy; override;
procedure StarteThread;
procedure BeendeThread;
end;
var
CSAbbruch: TCriticalSection;
implementation
destructor TThreadManager.Destroy;
begin
BeendeThread;
inherited Destroy;
end;
procedure TThreadManager.StarteThread;
begin
if Assigned(FMyThread) then
Exit; // Falls der Thread gerade läuft, soll er nicht nochmal gestartet werden
FMyThread := TMyThread.Create(True); // CreateSuspended = True
FMyThread.FreeOnTerminate := True;
{An dieser Stelle noch Werte übergeben}
FMyThread.Resume;
end;
procedure TThreadManager.BeendeThread;
begin
if not Assigned(FMyThread) then
Exit;
CSAbbruch.Acquire; // Damit kein Abbruch an einer kritischen Stelle erfolgt
FMyThread.Terminate;
FMyThread := nil;
CSAbbruch.Release;
end;
initialization
CSAbbruch := TCriticalSection.Create;
finalization
FreeAndNil(CSAbbruch);
Thread:
Delphi-Quellcode:
type
TMyThread = class(TThread)
private
procedure ArbeiteUnkritisch(...);
procedure ArbeiteKritisch(...);
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
begin
CoInitialize; // Deshalb habe ich überhaupt erst die Probleme ...
if Terminated then
begin
CoUninitialize;
Exit;
end;
ArbeiteUnkritisch(...);
if Terminated then
begin
CoUninitialize;
Exit;
end;
CSAbbruch.Acquire;
ArbeiteKritisch(...);
CSAbbruch.Release;
CoUninitialize; // ... bzw. eher deshalb
end;
procedure TMyThread.ArbeiteUnkritisch;
begin
{Hier wird gearbeitet und es steht natürlich haufenweise drin:}
if Terminated then
Exit;
end;
procedure TMyThread.ArbeiteKritisch;
begin
{Hier geschehen Dinge, die entweder gar nicht angefangen oder vollständig ausgeführt werden sollen,
deshalb steht hier auch nirgends "if Terminated then Exit"
und insbesondere erfolgt der Aufruf dieser Prozedur zwischen CSAbbruch.Acquire & .Release}
end;
Man führt also sämtliche Befehle an den Thread über den Umweg des ThreadManagers durch.
Um dann nochmal auf omatas Idee zurückzugreifen... dem kommt das folgende recht nahe:
Thread:
Delphi-Quellcode:
type
TMyThread = class(TThread)
private
procedure ArbeiteUnkritisch(...);
procedure ArbeiteKritisch(...);
protected
procedure Execute; override;
public
TerminateAndNil(var AReferenz: TMyThread);
end;
implementation
procedure TMyThread.Execute;
begin
CoInitialize; // Deshalb habe ich überhaupt erst die Probleme ...
if Terminated then
begin
CoUninitialize;
Exit;
end;
ArbeiteUnkritisch(...);
if Terminated then
begin
CoUninitialize;
Exit;
end;
CSAbbruch.Acquire;
ArbeiteKritisch(...);
CSAbbruch.Release;
CoUninitialize; // ... bzw. eher deshalb
end;
procedure TMyThread.ArbeiteUnkritisch;
begin
{Hier wird gearbeitet und es steht natürlich haufenweise drin:}
if Terminated then
Exit;
end;
procedure TMyThread.ArbeiteKritisch;
begin
{Hier geschehen Dinge, die entweder gar nicht angefangen oder vollständig ausgeführt werden sollen,
deshalb steht hier auch nirgends "if Terminated then Exit"
und insbesondere erfolgt der Aufruf dieser Prozedur zwischen CSAbbruch.Acquire & .Release}
end;
procedure TMyThread.TerminateAndNil(var AReferenz: TMyThread);
begin
AReferenz := nil;
Terminate;
end;
MainForm:
Delphi-Quellcode:
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FMyThread: TMyThread;
public
procedure StarteThread;
procedure BeendeThread;
end;
var
CSAbbruch: TCriticalSection;
implementation
procedure TMainForm.StarteThread;
begin
if Assigned(FMyThread) then
Exit;
FMyThread := TMyThread.Create(True); // CreateSuspended = True
FMyThread.FreeOnTerminate := True;
{Hier Werte übergeben}
FMyThread.Resume;
end;
procedure TMainForm.BeendeThread;
begin
if not Assigned(FMyThread) then
Exit;
CSAbbruch.Acquire; // Damit kein Abbruch an einer kritischen Stelle erfolgt
FMyThread.TerminateAndNil;
CSAbbruch.Release;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BeendeThread;
end;
initialization
CSAbbruch := TCriticalSection.Create;
finalization
FreeAndNil(CSAbbruch);
Dabei muss man allerdings aufpassen, dass man die Terminierung des Thread immer per
anfordert.
Der Knackpunkt war der, dass dafür gesorgt werden muss, dass die Refernz auf den Thread jeweils auf
zeigt, sofern der Thread gerade nicht läuft (bzw. bereits den Terminierungsbefehl erhalten hat).
Das ganze Schlamassel ist dadurch entstanden, dass ein Thread beim Terminieren teilweise ewig lang braucht, wenn er noch
aufruft. Darauf soll der Benutzer aber nicht warten.
Die entscheidende Frage, die sich nun daraus ergibt und beide Lösungen betrifft, ist letztlich die: Wird beim Schließen des Hauptfensters das
vom Thread noch durchgeführt? Wartet also die Anwendung mit dem Schließen so lange, bis auch der Thread terminiert hat, wobei nur der Benutzer nichts mehr davon sieht, oder killt die Anwendung beim Schließen des Hauptfensters rigoros den Thread, ohne auf dessen Terminierung zu warten?
Wäre schön, wenn jemand darauf eine Antwort hätte. (Es sei mir bitte verziehen, das das jetzt etwas Off-Topic geworden ist
)
Beste Grüße!
Robert