Delphi-PRAXiS

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)

Rob09 5. Okt 2011 22:34

Delphi-Version: 6

Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Hi!

Okay, folgendes:

Wenn ich prüfen möchte, ob ein Objekt existiert, dann kann ich ja einfach folgendes abfragen:

Delphi-Quellcode:
if Assigned(ReferenzAufObjekt) then ...
.

Das funktioniert aber nur dann, wenn ich beim zerstören des Objekts
Delphi-Quellcode:
FreeAndNil(ReferenzAufObjektObjekt)
verwende (oder halt
Delphi-Quellcode:
ReferenzAufobjekt := nil
).

Wenn ich allerdings das Objekt nur mit
Delphi-Quellcode:
ReferenzAufObjekt.Free
zerstöre, dann liefert
Delphi-Quellcode:
Assigned(...)
ja weiterhin True.

Gibt es denn irgendeine Möglichkeit, auch dann zu prüfen, ob das Objekt, auf das ReferenzAufObjekt zeigt, tatsächlich existiert oder schon zerstört wurde?

Beste Grüße!
Robert

Luckie 5. Okt 2011 22:43

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Ja, drauf zugreifen und wenn es kracht, existiert es nicht. aber wenn du um die Problematik weißt, warum gibst du es dann nicht ordentlich frei?

Sir Rufo 5. Okt 2011 22:48

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

Zitat von Luckie (Beitrag 1128679)
Ja, drauf zugreifen und wenn es kracht, existiert es nicht. aber wenn du um die Problematik weißt, warum gibst du es dann nicht ordentlich frei?

So richtig ordentlich frei geben macht er ja wohl, aber er hat womöglich an anderen Stellen die Referenz noch gespeichert und dann hat er sich dort den Boden unter den Füßen weggezogen.

Somit bleibt dann wohl nur ein TInterfacedObject oder das Anwendungsdesign nochmal zu überdenken.

Rob09 5. Okt 2011 22:58

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Das tu ich deshalb, weil ich einen TThread benutze, der sich durch
Delphi-Quellcode:
.FreeOnTerminate = True
selbst zerstört, wenn er fertig ist (vgl. http://www.delphipraxis.net/163566-c...ch-noetig.html). Dabei wird die Referenz auf selbigen aber nicht
Delphi-Quellcode:
nil
gesetzt.

An Anderer Stelle möchte ich prüfen, ob der Thread noch läuft - dafür würde ich das oben nachgefragte dann brauchen.

Problem ist, dass ich mich etwas scheue, in der
Delphi-Quellcode:
OnTerminate
-Prozedur des Threads die Referenz auf ihn
Delphi-Quellcode:
nil
zu setzen, da es durch
Delphi-Quellcode:
CoUninitialize
etwas dauern kann, bis der Thread terminiert. Da der Aufruf zum
Delphi-Quellcode:
Terminate
aus der
Delphi-Quellcode:
OnClose
-Prozedur des Hauptfensters kommen kann (das nicht
Delphi-Quellcode:
Thread.WaitFor
abwartet, bevor es schließt), habe ich etwas Angst, was dabei passieren könnte.

omata 5. Okt 2011 23:05

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Wie wäre es mit einem Event, dass beim Beenden des Threads eine Prozedur aufruft, die die globale Variable auf den Thread auf nil setzt?

Rob09 5. Okt 2011 23:14

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

Du meinst also beim "regulären" Fertigwerden des Threads? Leider kann es auch sein, dass der Thread mit .Terminate abgebrochen wird, ohne dass das Programm geschlossen wird. In dem Fall müsste dann auch dafür gesorgt werden, dass die Referenz nil gesetzt wird. Wenn der .Terminate-Aufruf dann allerdings doch beim Schließen des Hauptfensters geschieht, stehe ich wieder vor dem oben beschriebenen Problem.

Oder habei ch dich falsch verstanden?

omata 5. Okt 2011 23:19

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Ja, genauso meine ich das.
Überschreib doch Terminate und sorge so dafür, dass das Event eben doch vorher noch ausgeführt wird.

Rob09 5. Okt 2011 23:24

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Ah, sehr gute Idee! :thumb:

Werde mich morgen (also heute wenns hell wird :wink:) mal ransetzen und dann Bericht erstatten.

Vielen Dank an alle bis hierher und gute Nacht! :hi:
Robert

Rob09 5. Okt 2011 23:33

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Okay, dabei dann doch noch eine Frage:

Wenn ich aus dem MainThread heraus das .Terminate des NebenThreads aufrufe, führt dann der MainThread den Code, der dabei in eventuellen OnMyTerminate-Events steht, aus (was ich meine ist, ob dieser Code dann zuerst ausgeführt wird und erst danach die Befehle, die im MainThread nach NebenThread.Terminate noch kommen)?

omata 5. Okt 2011 23:49

AW: Prüfen, ob Referenz auf tatsächlich existentes Objekt zeigt
 
Die Befehle in der ThreadEnd-Prozedur werden nicht vom MainThread ausgeführt, sondern von NebenThread und damit ist ein Kritischer Abschnitt erforderlich oder ein Synchronize.

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 10:13 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