AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Anfängerfragen zu Threads (TThread)

Ein Thema von Dalai · begonnen am 3. Nov 2017 · letzter Beitrag vom 5. Nov 2017
Antwort Antwort
Seite 1 von 2  1 2      
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#1

Anfängerfragen zu Threads (TThread)

  Alt 3. Nov 2017, 21:21
Hallo *.*,

um die Reaktionsfähigkeit eines meiner Programme zu verbessern, denke ich gerade darüber nach, Threads zu verwenden, um bestimmte Funktionen von diesem ausführen zu lassen. Die Situation stellt sich folgendermaßen dar:
Delphi-Quellcode:
type
  TfmMain = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FObject : TComplexClass;
  end;

implementation

procedure TfmMain.FormCreate(Sender: TObject);
begin
    FObject:= TComplexClass.Create(ip);
    if FObject.Ping then begin
    [...]
    end;
end;
Die Ausführung von FObject.Ping kann aufgrund von mir nicht beeinflussbaren Faktoren sehr lange dauern (bei Nichterreichbarkeit der IP durchaus 20 Sekunden). Daher kam mir die Idee, diese Methode in einem Thread auszuführen. Da ich bisher noch nie mit Threads zu tun hatte, habe ich mich natürlich etwas angelesen, neben der Delphi-Hilfe unter anderem das Threads-Tut im Delphi-Treff und auch bei Wikibooks. Einige meiner Fragen wurden durch die Lektüre durchaus beantwortet. Meine Idee ist nun, eine Klasse TPingThread von TThread abzuleiten, und in dessen Execute-Methode irgendwie den Ping auszuführen und dessen Rückgabewert zu speichern. Wenn der Thread fertig ist, will ich im OnTerminate darauf reagieren, je nach Rückgabewert von Ping.

Aber ein paar wichtige Sachen sind mir noch unklar:
  • Wie übergebe ich an TPingThread.Create/Execute die Methode Ping? Ich würde ungern das komplette Objekt FObject übergeben. Oder wäre das der richtige Weg?
  • Wer setzt ReturnValue? Passiert das bereits in TThread oder müsste ich das in meiner Ableitung TPingThread tun?
  • Spricht etwas dagegen, in der Ereignisbehandlung OnTerminate direkt ReturnValue auszuwerten? Ich sehe keinen Sinn darin, WaitFor zu benutzen, denn zu diesem Zeitpunkt hat der Thread ja seine Arbeit bereits erledigt.

Auch diesmal hoffe auf Aufklärung mittels der hier vertretenen Expertise .

Grüße
Dalai
  Mit Zitat antworten Zitat
Redeemer

Registriert seit: 19. Jan 2009
Ort: Kirchlinteln (LK Verden)
1.017 Beiträge
 
Delphi 2009 Professional
 
#2

AW: Anfängerfragen zu Threads (TThread)

  Alt 3. Nov 2017, 22:36
Verstehe die Frage 1 nicht. Du schreibst doch Execute selber. Execute macht an sich überhaupt nichts.

Delphi-Quellcode:
procedure TMyThread.Execute;
var
  Dings: TDings;
begin
  Dings := TDings.Create();
  Dings.Bums();
end;
Janni
2005 PE, 2009 PA, XE2 PA
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#3

AW: Anfängerfragen zu Threads (TThread)

  Alt 3. Nov 2017, 23:06
Verstehe die Frage 1 nicht.
Dann versuche ich mal, daran etwas zu ändern.

Zitat:
Du schreibst doch Execute selber. Execute macht an sich überhaupt nichts.
Das ist mir schon klar. Aber sowas wie das
Zitat:
Delphi-Quellcode:
procedure TMyThread.Execute;
var
  Dings: TDings;
begin
  Dings := TDings.Create();
  Dings.Bums();
end;
- also eine Erzeugung von TComplexClass innerhalb des Threads - will ich vermeiden, weil das einen kompletten Umbau bedeuten würde. FObject ist mit Absicht ein Attribut von TfmMain, weil in einigen von dessen Methoden noch eine ganze Reihe anderer Operationen ausgeführt werden, Eigenschaften abgefragt etc - und zwar regelmäßig in Intervallen. FObject nur im Thread zu haben - da weiß ich als Anfänger gar nicht, wo ich anfangen soll, darüber nachzudenken, wie das funktionieren würde. Schließlich ist ein Thread beendet, sobald Execute abgeschlossen ist. Das Objekt muss aber so lange existieren, wie das Programm läuft.

Grüße
Dalai

Geändert von Dalai ( 3. Nov 2017 um 23:12 Uhr)
  Mit Zitat antworten Zitat
Aviator

Registriert seit: 3. Jun 2010
1.610 Beiträge
 
Delphi 10.3 Rio
 
#4

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 00:14
Dann übergib an den Thread doch einfach nur per Property oder im Constructor die IP-Adresse des Ziel-Rechners. Und dann führst du im Thread den Ping aus und gibst das Ergebnis bspw. per Event zurück. Dann musst du auch beim Auslösen des OnTerminate Events des Threads nicht die Daten selber abholen sondern bekommst sie als Parameter vom eigenen Event geliefert.
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#5

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 03:14
Dein Hinweis war möglicherweise der Schubs in die richtige Richtung. Du meinst also etwa in der Art:
Delphi-Quellcode:
type
  TPingThread = class(TThread)
  private
    { Private-Deklarationen }
    FFB: TComplexClass;
  protected
    procedure Execute; override;
  public
    constructor Create(const AHost: string);
    destructor Destroy; override;
    property ReturnValue;
  end;

constructor TPingThread.Create(const AHost: string);
begin
  FreeOnTerminate:= True;
  FFB:= TComplexClass.Create(AHost);
  inherited Create(False);
end;

destructor TPingThread.Destroy;
begin
  FFB.Free;
  inherited;
end;

procedure TPingThread.Execute;
begin
  // Sleep nur zu Zwecken des Debugging
  Sleep(100);
  if Assigned(FFB) then
      ReturnValue:= Integer(FFB.Ping);
end;
Und dann in TfmMain
Delphi-Quellcode:
procedure TfmMain.FormCreate(Sender: TObject);
begin
    // Die Zuweisung von FHostName lasse ich mal mangels Relevanz weg; ist ein privates Attribut der Klasse
    FObject:= TComplexClass.Create(FHostName);
    PingThreadStart;
end;

procedure TfmMain.PingThreadStart;
begin
    FPingThread:= TPingThread.Create(FHostName);
    FPingThread.OnTerminate:= PingThreadDone;
end;

procedure TfmMain.PingThreadDone(Sender: TObject);
var
  Lsucc: Boolean;
begin
    Lsucc:= Boolean(FPingThread.ReturnValue);
    if Lsucc then
        Self.Caption:= 'Connected to ' + FObject.HostName
    else
        MessageBox(Self.Handle, PChar(FObject.HostName + ' not available'), '', MB_OK or MB_ICONINFORMATION);
end;
Das funktioniert. Zwei Sachen stören mich:
  • Redundanz durch die Erzeugung der zweiten Instanz von TComplexClass in TPingThread und ggf. Zuweisung der nötigen Eigenschaften, auch wenn letzteres derzeit zum Glück nicht nötig ist.
  • Es gibt Speicherlecks, wenn der Thread vorzeitig terminiert (während TPingThread.Execute noch läuft):
    Code:
    ---------------------------
    Unexpected Memory Leak
    ---------------------------
    An unexpected memory leak has occurred. The unexpected small block leaks are:

    1 - 12 bytes: TObject x 1
    21 - 28 bytes: AnsiString x 1
    61 - 68 bytes: Unknown x 1
    69 - 76 bytes: TPingThread x 1
    269 - 284 bytes: TComplexClass x 1

    ---------------------------
    OK  
    ---------------------------
    Sind diese Lecks von Bedeutung?

Grüße
Dalai
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#6

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 08:50
In der Art..
Warum muß die eierlegende Wollmilchsauklasse dem Thread bekannt sein? Der Thread hat eine (kleine) Aufgabe zu erledigen, und dabei sollte er sich auf seinen Hinterhof beschränken, auf dem sich aber auch niemand anders herum treiben sollte. Und wenn er fertig ist, dies dem Mainthread mit einem Postmessage bekannt geben.

Gruß
K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
Aviator

Registriert seit: 3. Jun 2010
1.610 Beiträge
 
Delphi 10.3 Rio
 
#7

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 10:37
Hi Dalai,

was heißt "vorzeitig terminieren"? Wenn du das Programm etwa mit dem Schließen X beendest? Wenn dem so sein sollte, dann müsste deine Anwendung einen Shutdown Prozess initiieren und alle noch laufenden Threads "sauber" beenden. Möglicherweise kann dann das Beenden der Anwendung auch ein paar Sekunden dauern. Einen Thread direkt wegschießen halte ich nicht für besonders gut und wüsste auch wenn ich ehrlich bin nicht wie ich das machen sollte.

Ansonsten beachte noch den Hinweis von p80286. Ein Thread bearbeitet nur eine kleine Teilaufgabe und muss auch dementsprechend nur die Informationen bekommen die er zum Arbeiten benötigt.
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#8

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 14:31
Warum muß die eierlegende Wollmilchsauklasse dem Thread bekannt sein?
Diese Frage steht mit der bereits genannten Redundanz in Verbindung, und ist eine Sache, die mir auch nicht gefällt. Im Prinzip könnte der Thread alles mit der Instanz der Klasse machen. Genau das wollte ich verhindern und nur die Methode Ping in den Thread geben. Aber ich werd mir mal das Klassendesign anschauen, ob ich da etwas weiter unten ansetzen kann. TComplexClass beinhaltet ein paar weitere Klassen, hoffentlich ist eine davon für das Ping zuständig (nein, ich habe den Code von TComplexClass nicht geschrieben, nur etwas angepasst).

Zitat:
Und wenn er fertig ist, dies dem Mainthread mit einem Postmessage bekannt geben.
Macht es denn einen Unterschied, ob ich das Arbeitsende mittels PostMessage, eigenem TNotifyEvent oder OnTerminate bekanntgebe?

was heißt "vorzeitig terminieren"? Wenn du das Programm etwa mit dem Schließen X beendest?
Zum Beispiel.

Zitat:
Wenn dem so sein sollte, dann müsste deine Anwendung einen Shutdown Prozess initiieren und alle noch laufenden Threads "sauber" beenden.
Der Thread wird offenbar knallhart gekillt, ohne dass er davon etwas mitbekommt. Der Destruktur des Threads wird nicht durchlaufen (daher wohl auch die Lecks). Und nein, mein Code ruft an keiner Stelle FPingThread.Terminate oder ähnliches.

Zitat:
Einen Thread direkt wegschießen halte ich nicht für besonders gut und wüsste auch wenn ich ehrlich bin nicht wie ich das machen sollte.
Sehe ich auch so. Nur weiß ich nicht, wie ich das verhindern sollte, wenn der Delphi-Code das bereits tut.

Grüße
Dalai
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#9

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 16:49
Nach ein bisschen Umbau und der Nutzung nur jener Klasse, die wirklich nötig ist, sieht es nun so aus:
Delphi-Quellcode:
type
  TPingThread = class(TThread)
  private
    FClient: TClient;
    FOnPingDone: TNotifyEvent;
  protected
    procedure Execute; override;
  public
    constructor Create(const AHost: string);
    destructor Destroy; override;
    property OnPingDone: TNotifyEvent read FOnPingDone write FOnPingDone;
    property ReturnValue;
  end;
  
implementation
  
constructor TPingThread.Create(const AHost: string);
begin
  FOnPingDone:= nil;
  FreeOnTerminate:= True;
  FClient:= TClient.Create(AHost);
  FClient.Port:= 80;
  inherited Create(False);
end;

destructor TPingThread.Destroy;
begin
  FClient.Free;
  inherited;
end;

procedure TPingThread.Execute;
begin
  if Assigned(FClient) then
      ReturnValue:= Integer(FClient.IsAlive);
  if Assigned(FOnPingDone) then
      FOnPingDone(Self);
end;
und in TfmMain weise ich das OnPingDone-Event statt OnTerminate zu. Das Speicherleck beim vorzeitigen Beenden ist etwas kleiner:
Code:
---------------------------
Unexpected Memory Leak
---------------------------
An unexpected memory leak has occurred. The unexpected small block leaks are:

1 - 12 bytes: TObject x 1
21 - 28 bytes: AnsiString x 1
61 - 68 bytes: Unknown x 1
69 - 76 bytes: TPingThread x 1
93 - 100 bytes: TClient x 1

---------------------------
OK  
---------------------------
Kann man dagegen noch etwas tun? Ist zwar nicht besonders groß, aber stören tut's mich schon, und normalerweise räume ich den Krempel wieder auf, den ich erzeuge.

Grüße
Dalai
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#10

AW: Anfängerfragen zu Threads (TThread)

  Alt 4. Nov 2017, 18:49
Sorry, falls der Eindruck entsteht, ich würde spammen, aber ich glaube, ich hab's. Außerdem fiel mir auf, dass OnTerminate mittels Synchronize gerufen wird, das selbstdefinierte Event OnPingDone aber nicht. Das hab ich gleich korrigiert, sonst gibt's später böse Fehler.

Thread:
Delphi-Quellcode:
type
  TPingThread = class(TThread)
  private
    FClient: TClient;
    FOnPingDone: TNotifyEvent;
    procedure CallOnPingDone;
  protected
    procedure Execute; override;
  public
    constructor Create(const AHost: string);
    destructor Destroy; override;
    property OnPingDone: TNotifyEvent read FOnPingDone write FOnPingDone;
    property ReturnValue;
  end;

procedure TPingThread.CallOnPingDone;
begin
  if Assigned(FOnPingDone) then
    FOnPingDone(Self);
end;

constructor TPingThread.Create(const AHost: string);
begin
  FOnPingDone:= nil;
  FreeOnTerminate:= True;
  FClient:= TClient.Create(AnsiString(AHost));
  FClient.Port:= 80;
  inherited Create(False);
end;

destructor TPingThread.Destroy;
begin
  FClient.Free;
  inherited;
end;

procedure TPingThread.Execute;
begin
  if Assigned(FClient) then
      ReturnValue:= Integer(FClient.IsAlive);
  if NOT Terminated then
      Synchronize(CallOnPingDone);
end;
Main:
Delphi-Quellcode:
type
  TfmMain = class(TForm)
  private
    FObject : TComplexClass;
    FPingThread : TPingThread;
    FPingThreadAlive : Boolean;
  end;

procedure TfmMain.FormCreate(Sender: TObject);
begin
    FObject:= TComplexClass.Create(FHostName);
    PingThreadStart;
end;

procedure TfmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    if FPingThreadAlive then begin
        FPingThreadAlive:= False;
        FPingThread.Terminate;
    end;
end;

procedure TfmMain.PingThreadStart;
begin
    FPingThreadAlive:= True;
    FPingThread:= TPingThread.Create(FHostName);
    FPingThread.OnPingDone:= PingThreadDone;
end;

procedure TfmMain.PingThreadDone(Sender: TObject);
var
  Lsucc: Boolean;
begin
    FPingThreadAlive:= False;
    Lsucc:= Boolean(FPingThread.ReturnValue);
    if Lsucc then
        Self.Caption:= 'Connected to ' + FObject.HostName
    else
        MessageBox(Self.Handle, PChar(FObject.HostName + ' not available'), '', MB_OK or MB_ICONINFORMATION);
end;
Das gibt laut FastMM keine Lecks, der Destruktor des Threads wird durchlaufen, und alle drei Fälle (erreichbar, nicht erreichbar, vorzeitig terminiert) funktionieren. Falls jemand Einwände dagegen hat, immer her damit; ich lerne gern dazu (sofern ich's verstehe ).

Grüße
Dalai
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:09 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