Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi OnFinish --> FreeAndNil --> Exception (https://www.delphipraxis.net/198230-onfinish-freeandnil-exception.html)

berens 16. Okt 2018 11:03

OnFinish --> FreeAndNil --> Exception
 
Hallo,
ich versuche mich soweit es geht mittlerweile an der asynchronen Programmierung. D.h. nicht mehr "Tue etwas - *warten* - Auswerten" sondern "Tue etwas, und wenn Du fertig bist, rufe die-und-die Prozedur zurück".

Ich habe einen ClientSocket der mit einem Netzwerkgerät kommuniziert. Sobald er eine Antwort erhalten hat, speichert er das Ergebnis, trennt die Verbindung und kann dann freigegeben werden (ich habe ja jetzt meine Infos und brauche den ClientSocket nun nicht mehr).

Delphi-Quellcode:
procedure TFoo.ClientSocket1Disconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  ResultText := 'Getrennt von ' + Socket.RemoteAddress;
  NotifyFinish;
end;

procedure TFoo.NotifyFinish;
begin
  if assigned(OnFinish) then begin
    OnFinish(Self);
  end;
end;
Delphi-Quellcode:
procedure TForm2.SendFinished(Sender: TObject);
var
  cs: TFoo;
begin
  if Sender.InheritsFrom(TFoo) then begin
    cs := TFoo(Sender);
    Memo1.Lines.Add('ResultText: ' + cs.ResultText);
    Memo1.Lines.Add('ResultData: ' + cs.ResultData);
    Memo1.Lines.Add('Result: ' + BoolToStr(cs.Success, True));
    FreeAndNil(cs);
  end;
end;
Der ClientSocket agiert also unabhängig vom Hauptprogramm (bzw. "form2"), und löst auch brav TForm2.SendFinished aus, die Infos werden korrekt im Memo angezeigt.

Im Step-By-Step Debugging bin ich nun nach dem Disconnect mit der Zeile "NotifyFinish" durch, und IN der "end;" Zeile von ClientSocket1Disconnect schmeißt er mir nun die Exception um die Ohren. Die Fehlermeldung Socket 10038 die ich erhalte bedeutet letzt endlich, dass Ich/Windows auf einen Socket zugreife, den es nicht mehr im RAM gibt. Ist ja eigentlich auch klar, ich habe den kompletten ClientSocket ja auch eben mit FreeAndNil freigegeben...

Die Ursache für dieses eigentliche Problem ist mit ja generell bekannt. In der ParameterListe von ClientSocket1Disconnect steht ja ein Verweis auf "Socket: TCustomWinSocket", und nach Ende dieser Prozedur wird entweder mein Hauptobjekt, oder zumindest der übergebene Socket nochmal(?) von der Garbage-Collection oder sonstweshalb freigegeben oder was auch immer.

Die Frage ist nun, wie macht man es richtig? Ich einem früheren Thema von mir habe ich ja beigebracht bekommen, dass ein Objekt sich nicht selbst freigeben kann/darf/soll. Also muss ich über ein Event der übergeordneten Komponente mitteilen, dass diese Komponente nun ihre Arbeit beendet hat und freigegeben werden kann. Aus Prozeduren heraus, die mehr als nur "Sender: TObject;" als Parameter haben, darf man das scheinbar nicht? Eine andere private Prozedur aufrufen, die die übergeordnete Komponente zum freigeben benachrichtigt bringt ja auch nichts, weil ich nach Abwicklung dieser Prozedur ja wieder bzw. immernoch in ClientSocket1Disconnect bin, und das selbe Problem gerade wieder besteht. Wie rufe ich nun die Benachrichtigung auf, ohne noch einen auf den Stack draufzulegen?

Die einzige Möglichkeit die mir einfällt, wäre bei ClientSocket1Disconnect einen Timer der selben Komponente zu stellen, der nach 1ms triggert und dann das OnFinish auslöst. Dann wäre zumindest das Problem mit der Socket-Exception gelöst (falls es klappt). Aber ist das die richtige und/oder elegante Weise? Kann ich mir nicht vorstellen...

Bernhard Geyer 16. Okt 2018 11:28

AW: OnFinish --> FreeAndNil --> Exception
 
Ein Objekt im Eventhandling freigeben ist, sagen wir mal so, "mehr als Mutig".
I.d.R. wird es krachen, da beim "Rückabarbeiten" des Event-Kette irgendwo noch auf Eigenschaften des schon freigegebenen Objektes zugegriffen wird.

berens 16. Okt 2018 11:36

AW: OnFinish --> FreeAndNil --> Exception
 
Code:
I.d.R. wird es krachen, da beim "Rückabarbeiten" des Event-Kette irgendwo noch auf Eigenschaften des schon freigegebenen Objektes zugegriffen wird.
Ja, das ist richtig, ich hoffe ich konnte im ersten Beitrag vermitteln, dass ich selbst (schmerzhaft) zu dieser Erkenntnis gelangt bin.

Die Frage ist halt: Wie macht man es richtig?

Weil wenn man nicht Ereignis-bezogen freigeben kann ("Ich bin fertig - lösch mich") muss man ja in zeitlichen Intervallen prüfen "Wenn fertig, dann löschen". Das muss doch auch anders gehen, oder irre ich mich?

Edit:
Ich habe es jetzt tatsächlich so "gelöst", dass ClientSocket1Disconnect einen Timer startet, der nach 1 ms das NotifyFinish aufruft. Gut - Nicht Gut? Kann es sein, dass innerhalb der 1 ms der ClientSocket immer noch nicht fertig "verarbeitet" ist, obwohl nach dem Stellen des Timers keine Programmierzeile mehr ist, und es deshalb unerwartete Probleme gibt?

freimatz 16. Okt 2018 11:51

AW: OnFinish --> FreeAndNil --> Exception
 
Timer sind in 99% der Fälle nicht gut. in der Regel sollte der, der eine Klasse erzeugt diese auch wieder freigeben. Kannst du nicht "der" ein Event schicken dass diese das macht?

berens 16. Okt 2018 12:26

AW: OnFinish --> FreeAndNil --> Exception
 
Zitat:

Kannst du nicht "der" ein Event schicken dass diese das macht?
Ja, aber wann und wie löse ich dieses Event aus? Das ist ja die Kernfrage.

Bei ClientSocket1Disconnect kann ich das ja nicht tun. Woher weiß ich denn, wann das Event ausgelöst werden soll, wenn nicht über dieses Ereignis?

Das einzige, ansatzweise "professionelle" Verfahren was ich mir hierfür vorstellen könnte, ist, dass SendFinished NICHT FreeAndNil aufruft, sondern den Sender (cs) merkt (z.B. in einer ComponentList speichern). Eine -wie auch immer aufzurufende- (wieder das Kernproblem!) Prozedur schaut nach, ob in der ComponentList Einträge stehen und löscht diese Komponente dann mit FreeAndNil. Alternativ bekommt TFoo einfach eine Boolean-Variable, ob Fertig oder nicht, und TForm2 prüft dann halt die Boolean-Variable, und löscht die Komponente dann. Trotzdem muss diese "Löschroutine" von TForm2 über einen Timer oder sporadisch im Rahmen anderer Prozeduren aufgerufen werden, damit die Komponenten TFoo freigegeben werden können. Wird wohl so klappen, aber ob das dem Konzept der Asynchronen Programmierung entspricht (ernstgemeinte Frage)?

ChrisE 16. Okt 2018 12:33

AW: OnFinish --> FreeAndNil --> Exception
 
Hallo,

also prinzipiell läßt sich das über mehrere Ansätze lösen.

1. Ansatz - dem Objekt sagen es soll sich später freigeben
--> Das Objeckt soll sich abarbeiten und den selber Freigeben. Aber nur wenn man es dazu auffordert (DoTerminate := TRUE;)

2. Ansatz - entkoppplung "der Botschaft" vom Objekt-Kontext
a) PostMessage mit WM_MEINE_MESSAGE an z.B. Form
Delphi-Quellcode:
const WM_MEINE_MESSAGE = WM_USER +1;
//...
TForm2.
procedure WMMeineMessage(var Message: TMessage);message WM_MEINE_MESSAGE;
//...
b) Anonymer Thread
Delphi-Quellcode:
procedure ExecuteInAnonymThread(AThreadProc: TProc; ADoSyncronized: Boolean; ADelay: Int64);
begin
  TThread.CreateAnonymousThread(
    procedure
    begin
      Sleep(ADelay);
      if ADoSyncronized then
      begin
        TThread.Synchronize(nil,
          procedure
          begin
            AThreadProc;
          end);
      end else begin
        AThreadProc;
      end;
    end).Start;
end;
Vielleicht bringt dich das ja weiter

Gruß, Chris

Rollo62 17. Okt 2018 12:41

AW: OnFinish --> FreeAndNil --> Exception
 
Ich würde mir mal anschauen wie das TMessage und TMessageManager gelöst haben.

Die benutze ich an zig Stellen, und das arbeitet sehr zuverlässig mit und ohne Threads.

Rollo

QuickAndDirty 17. Okt 2018 16:39

AW: OnFinish --> FreeAndNil --> Exception
 
Delphi-Quellcode:
Procedure QueueMethod(aThreadMethod:TThreadMethod);
Begin
      // Abgeschaut aus UNIT FMX.FORMS
      // procedure ReleaseForm(const AForm: TCommonCustomForm);
      // Wir müssen das immer so machen wie die das dort auch machen.
      // Unter der Annahme dass die FMX Bibliotheken fehlerfrei sind....

      {$IFDEF ANDRODID}
      TThread.CreateAnonymousThread(
        Procedure
        Begin
          TThread.CurrentThread.Queue(nil,AthreadMethod);
        end;
      );
      {$ELSE}
       TThread.CurrentThread.ForceQueue(nil,aThreadMethod);
      {$ENDIF}
end;

TThread.CurrentThread.ForceQueue(nil,aThreadMethod );
Macht unter Windows im Hauptthread, dass die Methode erst nach dem "abspielen" aller anderen Messages aufgerufen wird...
Dieser Mechanismus reagiert also evtl. unerwünscht auf Application.processmessages...
Denn innerhalb dieses Aufrufs würde die Methode auch ausgeführt...


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:30 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