Einzelnen Beitrag anzeigen

Rob09

Registriert seit: 14. Aug 2007
58 Beiträge
 
Delphi 6 Personal
 
#13

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt

  Alt 6. Okt 2011, 20:06
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
Code:
.TerminateAndNil
anfordert.

Der Knackpunkt war der, dass dafür gesorgt werden muss, dass die Refernz auf den Thread jeweils auf
Code:
nil
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
Code:
CoUnitialize
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
Code:
CoUninitialize
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
  Mit Zitat antworten Zitat