Delphi-PRAXiS
Seite 2 von 2     12   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt (https://www.delphipraxis.net/163588-pruefen-ob-referenz-auf-tatsaechlich-existentes-objekt-zeigt.html)

sx2008 6. Okt 2011 00:05

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Es muss einen geben, der ist der "Chef" bzw. Erzeuger und Zerstörer des Objekts.
Im Zweifel ist dies das Formular, von dem aus die Threads gestartet wurden.
Die Abfolge ist wie folgt:
1. Formular erzeugt das Objekt (z.B. Datanbank-Objekt, TCP-Socket,...)
2. Formular erzeugt das Threadobjekt (CreateSuspended=True!!) und merkt sich das Threadobjekt
3. Formular übergibt das Objekt von 1. dem Thread (über ein Property)
4. Formular startet den Thread mit Aufruf thread.Resume

Falls nun das Formular zerstört wird muss Folgendes getan werden
5. Formular signalisiert dem Thread sich zu beenden (thread.Terminate)
sollte der Thread schon zuende sein, kann 5. und 6. übersprungen werden
Wichtig: der Thread muss immer wieder prüfen, ob Terminated=True ist und falls ja,
sofort die Execute-Methode verlassen
6. Formular wartet auf das Ende des Threads
7. Formular entsorgt alles was es selbst erzeugt hat; also das Objekt und auch das Threadobjekt

Sir Rufo 6. Okt 2011 01:09

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Ich würde das so umsetzen:

Hier der Erzeuger (die MainForm):
Delphi-Quellcode:
unit view.Server.Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class( TForm )
    Button1 : TButton;
    procedure Button1Click( Sender : TObject );
    procedure FormCloseQuery( Sender : TObject; var CanClose : Boolean );
  private
    fThreadDemo :      TThread;
    fFormCloseRequest : Boolean;
    procedure ThreadDestroy( Sender : TObject );
  public

  end;

var
  Form1 : TForm1;

implementation

uses
  thread.Demo;

{$R *.dfm}

procedure TForm1.Button1Click( Sender : TObject );
begin
  if Assigned( fThreadDemo )
  then
    Exit;

  fThreadDemo := TThreadDemo.Create( True );
  with TThreadDemo( fThreadDemo ) do
    begin
      FreeOnTerminate := True;
      OnDestroy      := Self.ThreadDestroy;
      Start;
    end;
end;

procedure TForm1.FormCloseQuery( Sender : TObject; var CanClose : Boolean );
begin
  if Assigned( fThreadDemo )
  then
    begin

      fThreadDemo.Terminate;

      if not fFormCloseRequest
      then
        begin
          fFormCloseRequest := True;
          with TPanel.Create( Self ) do
            begin
              Parent := Self;
              Left   := 0;
              Top    := 0;
              Width  := Self.ClientWidth;
              Height := Self.ClientHeight;
              Caption := 'Wir müssen noch auf den Thread warten!';
            end;
        end;
      CanClose := False;
    end;
end;

procedure TForm1.ThreadDestroy( Sender : TObject );
begin
  if fThreadDemo = Sender
  then
    begin
      fThreadDemo := nil;
      if fFormCloseRequest
      then
        Close;
    end;
end;

end.
und hier der Thread
Delphi-Quellcode:
unit thread.Demo;

interface

uses
  Classes;

type
  TThreadDemo = class( TThread )
  private
    fOnDestroy : TNotifyEvent;
  protected
    procedure Execute; override;
    procedure DoOnDestroy;
  public
    destructor Destroy; override;
    property OnDestroy : TNotifyEvent
      read  fOnDestroy
      write fOnDestroy;
  end;

implementation

{ TThreadDemo }

destructor TThreadDemo.Destroy;
begin
  Synchronize( DoOnDestroy );
  inherited;
end;

procedure TThreadDemo.DoOnDestroy;
begin
  if Assigned( OnDestroy )
  then
    OnDestroy( Self );
end;

procedure TThreadDemo.Execute;
begin
  while not Terminated do
    Sleep( 5000 );
end;

end.

Rob09 6. Okt 2011 19:06

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
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 :duck:)

Beste Grüße!
Robert

Sir Rufo 6. Okt 2011 19:17

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Leite den ThreadManager mal von TComponent ab und beim Erzeugen setzt du den Owner auf Application.
Dann kannst du dir das FreeAndNil in der MainForm auch sparen.

Die CS global in der Unit wenn du nur einen Thread starten willst ok, ansonsten verschieb die CS in den Thread.

Namenloser 6. Okt 2011 19:32

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich hab vor einiger Zeit mal was gebastelt, wo mit allerlei Tricks Objekte bei ihrer Freigabe automatisch alle Referenzen (wofür leider ein bestimmter Typ verwendet werden muss, mit normalen Objektreferenzen oder Pointern klappt es nicht), die auf sie zeigen auf
Delphi-Quellcode:
nil
setzen. Das ganze ist experimentell, für den produktiven Einsatz würde ich es nicht empfehlen. War auch eher eine Machbarkeitsstudie, die im Rahmen eines ähnliches Threads wie diesem vor einiger Zeit entstanden ist.

Aber ganz cool finde ich es trotzdem, deshalb hänge ich es mal an.

stahli 6. Okt 2011 19:41

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

Meintest Du diesen?
Ich hatte mir einen Link bisher verkniffen, da es durch die Thread-Verwendung wohl nicht ganz passt...

Rob09 8. Okt 2011 14:20

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Okay, danke euch allen; glaube, ich bin jetzt ganz zufrieden :wink:

Zur Ergänzung ist mir noch aufgefallen, dass in den Code-Vorschlägen in meinem letzten Post noch Nachbesserungsbedarf besteht. Und zwar muss folgendes noch eingeschoben werden:

Delphi-Quellcode:
procedure TMyThread.Execute;
begin
  ...
  CSAbbruch.Acquire;
  // ANFANG EINSCHUB ...
  if Terminated then
  begin
    CoUninitialize; //*
    CSAbbruch.Release; //*
    Exit;
  end;
  // ... ENDE EINSCHUB
  ArbeiteKritisch(...);
  ...
Wenn man beide //*-Zeilen in dieser Reihenfolge stehen lässt, kann es natürlich sein, dass sich das Programm beim Beenden ein bisschen Zeit lässt, da CoUninitialize ein bisschen braucht. Wenn man die Reihenfolge umkehrt, geht alles fix. Allerdings weiß ich nicht, ob das dann noch wirklich "sauber" ist. Dazu mache ich gleich noch einen Thread (ein "Thema" :wink:) auf... (hier der Link: http://www.delphipraxis.net/163649-p...es-sauber.html)

Beste Grüße!
Robert

Rob09 8. Okt 2011 19:44

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
:wall: Habe bei dem Code-Vorschlag, der den TThreadManager nutzt, übrigens auch nicht daran gedacht, dass der Thread ja auch regulär terminieren könnte, wobei dann auch FMyThread := nil gesetzt werden müsste. :coder2:


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:18 Uhr.
Seite 2 von 2     12   

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