Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Laufende whileschleife auf Knopfdruck unterbrechen (https://www.delphipraxis.net/183917-laufende-whileschleife-auf-knopfdruck-unterbrechen.html)

HL92 13. Feb 2015 13:27

Laufende whileschleife auf Knopfdruck unterbrechen
 
Hallo zusammen,

eigentlich dachte ich es wäre trivial aber dem scheint nicht so..
Folgende Situation:

Ich habe eine whileschleife:

Delphi-Quellcode:
while a=b and c=true
begin
..
einige anweisungen bliblablubb
..

c=PackageProcess.GetConnectstate
end
An anderer Stelle habe ich
Delphi-Quellcode:
procedure TForm_WLNConnectionNMEA.Button_DisconnectClick(Sender: TObject);
begin
  PackageProcess.SetConnectStateFalse;
  PackageProcess.DisconnectFromHost(Memo,IdTCPClient1);

end;
Jetzt zu meinem Problem:
Ich kann die whileschleife nicht per Knopfdruck beenden. Der ConnectState wird nicht aktualisiert weil ich überhaupt nicht mehr in die ButtonClick Methode reinkomme sobald die Schleife läuft.

Ich brauche also eine Möglichkeit, dass die Knopfdruckmethode höchste Priorität hat und
Sofort ausgeführt wird sobald ich den Knopf drücke. Oder gibt es eine bessere Methide?
Kann mir jemand helfen?

stahli 13. Feb 2015 13:41

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Das ist etwas kompliziert:

Wenn Deine Schleife läuft ist Dein Projekt mit der beschäftigt und kommt nicht dazu, die Formularereignisse abzuhandeln. Wann soll es das auch tun?

Wenn Du das willst, musst Du dem Formular extra Rechenzeit zur Verfügung stellen. Das geht am einfachsten mit Application.ProcessMessages z.B. am Ende jeden Schleifendurchlaufs.

Das bremst natürlich die Geschwindigkeit des Schleifendurchlaufs aus. Das Projekt muss ja immer zwischendurch die Ereignisse behandeln. Manchmal ruft man das daher auch nur jeden 1000ten Durchlauf auf oder so.

Man muss natürlich auch darauf achten, dass man nichts zerstört oder verändert, was in der Schleife benutzt wird.

Besser wäre grundsätzlich, für jeden Prozess eigene Threads zu benutzen aber für den Anfang geht auch die Lösung mit ProcessMessages.

HL92 13. Feb 2015 14:00

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Vielen dank für die schnelle Reaktion.
Ich hab die Anweisung Application.ProcessMessages bereits in der Schleife.

Er liest dann zwar den Status aus aber da der Knopfdruck diesen nicht verändert
sondern auf das knopfdrücken garnicht reagiert geht die Schleife direkt weiter.

Ich habe schon überlegt zu invertieren, dass ich den Status von c in der Schleife
auf false setze und dann eine Art:

"derKnopfwurdenichtgedrückt" wieder zuück.

Denkbar oder komplett umbauen und mit threads lösen?
wie kann ich das für mein Problem nutzen?

stahli 13. Feb 2015 14:09

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Die Informationen sind nicht sehr aussagekräftig.

Mein Vorschlag:

Bau Dir ein kleines Demoprojekt das nur eine Schleife und einen Button zum starten und zum abbrechen implementiert.
So wird es wohl am einfachsten, die Abläufe nachzuvollziehen und zu lernen.

Wenn Du das nicht willst, dann schau zumindest mal, ob Dein ButtonClick abgearbeitet wird (Haltepunkt setzen).

Du kannst natürlich hier auch mal Dein Projekt als Zip anhängen. Aber ich denke, da wird einiges noch recht suboptimal laufen. ;-)


Mit Threads würde ich jetzt an Deiner Stelle noch nicht anfangen.

mm1256 13. Feb 2015 14:12

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Ich würde es erst mal mit der SuFu versuchen. "schleife unterbrechen" und schon hast du eine Menge Vorschläge :thumb:

Redeemer 13. Feb 2015 14:35

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Ich nehme das hier:
Code:
if GetAsyncKeyState(VK_ESCAPE) < 0 then
Exit;
Problem ist, dass man die Taste je nachdem, wie viel er in der Schleife macht, etwas länger drücken muss, bis die SChleife halt an dem Teil vorbeikommt.

Dejan Vu 14. Feb 2015 09:11

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Ich verwende das:
Delphi-Quellcode:
Type
  TMyForm = class (TForm)
   
    Procedure ButtonAbbruchClick(Sender : TObject);
...
    FSchleifenAbbruchErwuenscht : Boolean;
    Property SchleifenAbbruchErwuenscht : Boolean Read FSchleifenAbbruchErwuenscht Write SetSchleifenAbbruchErwuenscht;
  ...
    Procedure MeineSchleife;
  end;

Procedure TMyForm.SetSchleifenAbbruchErwuenscht (value : Boolean);
begin
  ButtonAbbruch.Enabled := not Value;
  FButtonAbbruch := Value;
End;

Procedure TMyForm.MeineSchleife;
begin
  SchleifenAbbruchErwuenscht := false;
  try
    while true do begin
       Application.ProcessMessages;
       if SchleifenAbbruchErwuenscht then break;
       DoSomething();
    end;
  finally
    SchleifenAbbruchErwuenscht := false;
  end
end;

Procedure TMyForm.ButtonAbbruchClick(Sender : TObject);
Begin
  SchleifenAbbruchErwuenscht := true;
end;
Durch den Aufruf von
Delphi-Quellcode:
Application.ProcessMessages
wird auch der Click-Handler des
Delphi-Quellcode:
ButtonAbbruch
ausgeführt. Dieser zeigt nun an, das ein Schleifenabbruch erwünscht ist.
Die Schleife bekommt das mit und kann sich beenden.

Diese Property steuert gleichzeitig, ob der Button überhaupt klickbar ist.

BadenPower 14. Feb 2015 13:47

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Zitat:

Zitat von Dejan Vu (Beitrag 1289846)
Delphi-Quellcode:
  finally
    SchleifenAbbruchErwuenscht := false;
  end
...

Diese Property steuert gleichzeitig, ob der Button überhaupt klickbar ist.

Solltest Du nicht besser im Finally-Abschnitt den Wert auf "true" setzen?

Wenn DU dort "false" setzt, dann ist der Button Enabled und signalisiert so fälschlicherweise dem Anwender, dass eine Schleife laufen würde.

Sir Rufo 14. Feb 2015 14:10

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Dieses lässt sich insgesamt auch sehr schön generalisieren.

Eigentlich habe ich eine Aktion, die ausgeführt werden soll. Diese kann gestartet und während der Ausführung abgebrochen werden. Schön ist dann noch eine Benachrichtigung am Ende der Aktion.

Erst vor kurzem wurde hier das Command-Pattern angesprochen, dass hervorragend dazu passt:
Delphi-Quellcode:
ICommand = interface
  function CanExecute : Boolean;
  procedure Execute;
end;
Eine Aktion würde demnach so aussehen:
Delphi-Quellcode:
TActionResult = ( arCancelled, arException, arFinished );
TActionFinishedEvent = procedure ( Sender : TObject; ActionResult : TActionResult );

IAction = interface
  property StartCommand : ICommand;
  property CancelCommand : ICommand;
  property OnFinished : TActionFinishedEvent;
end;
Genau damit würde ich ansetzen. Ob die dann mit einem Thread oder eben mit
Delphi-Quellcode:
Application.ProcessMessages
ist erst mal schnurz (Threads wären hier aber schon die richtige Wahl).

Dejan Vu 14. Feb 2015 16:15

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Zitat:

Zitat von BadenPower (Beitrag 1289867)
Solltest Du nicht besser im Finally-Abschnitt den Wert auf "true" setzen?

Vermutlich. Achte mal auf die Zeit, wann ich das geschrieben habe. Da war ich -glaube ich- noch nicht einmal wach.

Sir Rufo 14. Feb 2015 16:29

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Ich habe noch ganz vergessen zu erwähnen, dass es noch ein Konzept gibt, dass weder Threads noch
Delphi-Quellcode:
Application.ProcessMessages
benötigt.

Jede Anwendung geht immer wieder in den Idle-State. Das bekommt man mit, wenn man
Delphi-Quellcode:
Application.OnIdle
oder in der VCL
Delphi-Quellcode:
TApplicationEvents.OnIdle
bestückt.

Anstatt also jetzt die Schleife in einem Rutsch auszuführen, legt man den nächsten Arbeitsschritt fest und wartet, bis man durch
Delphi-Quellcode:
OnIdle
wieder aufgerufen wird. Jetzt erfolgt die Abarbeitung der Schritte im Main-Thread, nach jedem Schritt werden alle Nachrichten der Anwendung abgearbeitet und im Anschluss daran kommt der Idle-Event, der dann den nächsten Schritt anstösst.

Das geht zwar langsamer als in einem Thread, blockiert die Anwendung aber nicht so heftig wie die Schleife in ein Rutsch auszuführen. ;)

HL92 16. Feb 2015 10:28

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Moin zusammen,

vielen Dank für die Antworten.
Ich habe es in dieser Form versucht. Das Problem klärt sich dadurch aber noch nicht:

Delphi-Quellcode:
procedure TForm_WLNConnectionNMEA.IdTCPClient1Connected(Sender: TObject);
  begin
    a:=true;
    PackageProcess.clear(Memo);
    Memo.Lines.Add('connected');
    Button_Disconnect.Enabled := true;

   while a=true do
      begin

        Application.ProcessMessages;
       
        PackageProcess.ReceiveString(IdTCPClient1);
        PackageProcess.selectNMEA ();
        PackageProcess.showmessage(Memo);

        a:=PackageProcess.GetConnectState;

      end
  end;

procedure TForm_WLNConnectionNMEA.Button_DisconnectClick(Sender: TObject);
begin
  PackageProcess.SetConnectStateFalse;
  PackageProcess.DisconnectFromHost(Memo,IdTCPClient1);

end;
Ich kann allerdings auch weiterhin den Disconnectknopf nur dann mit Reaktion drücken wenn die Schleife nicht läuft. Unter untenstehendem Link ist einmal das gezipte Projekt. Entscheidender Part ist CForm_WLNNMEA.

https://www.dropbox.com/s/b09b0wvn82...EA1.3.zip?dl=0

Vielleicht sieht ja jemand meinen Fehler. Als nächstes test ich sonst den OnIdle Vorschlag.

Sir Rufo 16. Feb 2015 19:31

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Da ich per PN danach gefragt wurde, hier eine (schnell dahin getippte) Variante für so einen
Delphi-Quellcode:
IdleJob
:

Die Basis:
Delphi-Quellcode:
unit uIdleJob;

interface

uses
  {System.}Classes, {TNotifyEvent}
  {Vcl.}AppEvnts; {TApplicationEvents}

type
  TJobFinishState = ( jfsCancelled, jfsFinished );
  TFinishNotifyEvent = procedure( Sender: TObject; State: TJobFinishState ) of object;

  TIdleJob = class abstract
  private
    FAppEvnt: TApplicationEvents;
    FOnFinish: TFinishNotifyEvent;
    FOnStep: TNotifyEvent;
    procedure HandleOnIdle( Sender: TObject; var Done: Boolean );
    function GetIsRunning: Boolean;
    procedure DoOnFinish( AState: TJobFinishState );
    procedure DoOnStep;
  protected
    procedure DoStart; virtual;
    procedure DoStep; virtual; abstract;
    procedure DoStop; virtual;
    procedure JobFinished( NotifyLastStep: Boolean = True );
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;

    procedure Start;
    procedure Stop;

    property IsRunning: Boolean read GetIsRunning;
    property OnFinish: TFinishNotifyEvent read FOnFinish write FOnFinish;
    property OnStep: TNotifyEvent read FOnStep write FOnStep;
  end;

implementation

{ TIdleJob }

procedure TIdleJob.AfterConstruction;
begin
  inherited;
  FAppEvnt := TApplicationEvents.Create( nil );
end;

procedure TIdleJob.BeforeDestruction;
begin
  FAppEvnt.Free;
  inherited;
end;

procedure TIdleJob.DoOnFinish( AState: TJobFinishState );
begin
  if Assigned( FOnFinish )
  then
    FOnFinish( Self, AState );
end;

procedure TIdleJob.DoOnStep;
begin
  if Assigned( FOnStep )
  then
    FOnStep( Self );
end;

procedure TIdleJob.JobFinished( NotifyLastStep: Boolean );
begin
  FAppEvnt.OnIdle := nil;
  if NotifyLastStep
  then
    DoOnStep;
  DoOnFinish( jfsFinished );
end;

procedure TIdleJob.DoStart;
begin
end;

procedure TIdleJob.DoStop;
begin
end;

function TIdleJob.GetIsRunning: Boolean;
begin
  Result := Assigned( FAppEvnt.OnIdle );
end;

procedure TIdleJob.HandleOnIdle( Sender: TObject; var Done: Boolean );
begin
  DoStep( );
  if IsRunning
  then
    DoOnStep;
end;

procedure TIdleJob.Start;
begin
  if IsRunning
  then
    Exit;

  FAppEvnt.OnIdle := HandleOnIdle;
  DoStart;
  DoOnStep;
end;

procedure TIdleJob.Stop;
begin
  if not IsRunning
  then
    Exit;

  FAppEvnt.OnIdle := nil;
  DoStop;
  DoOnFinish( jfsCancelled );
end;

end.
Eine Loop Ableitung:
Delphi-Quellcode:
unit uMyLoopJob;

interface

uses
  System.SysUtils,
  uIdleJob;

type
  TMyLoopJob = class( TIdleJob )
  private
    FFrom, FTo, FStep: Integer;
    FCurrent: Integer;
  protected
    procedure DoStart; override;
    procedure DoStep; override;
    procedure DoStop; override;
  public
    constructor Create( const AFrom, ATo, AStep: Integer );

    property Current: Integer read FCurrent;
  end;

implementation

{ TMyLoopJob }

constructor TMyLoopJob.Create( const AFrom, ATo, AStep: Integer );
begin
  inherited Create;
  FFrom := AFrom;
  FTo := ATo;
  FStep := AStep;
end;

procedure TMyLoopJob.DoStart;
begin
  inherited;
  FCurrent := FFrom;
end;

procedure TMyLoopJob.DoStep;
begin
  inherited;
  Sleep( 20 ); // Wir simulieren mal ein bisserl Rechenlast
  Inc( FCurrent );
  if FCurrent >= FTo
  then
    begin
      FCurrent := FTo;
      JobFinished( True );
    end;
end;

procedure TMyLoopJob.DoStop;
begin
  inherited;
  // nichts zu tun hier
end;

end.
und jetzt mit einer Form zusammen
Delphi-Quellcode:
unit Unit2;

interface

uses
  uIdleJob, uMyLoopJob,

  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;

type
  TForm2 = class( TForm )
    StartLoopButton: TButton;
    StopLoopButton: TButton;
    ProgressBar1: TProgressBar;
    Label1: TLabel;
    procedure StartLoopButtonClick( Sender: TObject );
    procedure StopLoopButtonClick( Sender: TObject );
  private
    FLoopJob: TMyLoopJob;
    procedure LoopJobStep( Sender: TObject );
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}
{ TForm2 }

procedure TForm2.AfterConstruction;
begin
  inherited;
  FLoopJob := TMyLoopJob.Create( 1, 100, 1 );
  FLoopJob.OnStep := LoopJobStep;
end;

procedure TForm2.BeforeDestruction;
begin
  FLoopJob.Free;
  inherited;
end;

procedure TForm2.StartLoopButtonClick( Sender: TObject );
begin
  FLoopJob.Start;
end;

procedure TForm2.StopLoopButtonClick( Sender: TObject );
begin
  FLoopJob.Stop;
end;

procedure TForm2.LoopJobStep( Sender: TObject );
begin
  Label1.Caption := IntToStr( FLoopJob.Current );
  ProgressBar1.Position := FLoopJob.Current;
end;

end.
Was man hier schön sieht:

Die Basis kümmert sich um den gesamten Verwaltungskram, der konkrete Job nur noch um sich selber und die Form steuert/reagiert nur noch.
Oder anders ausgedrückt, je konkreter ich werde umso weniger Code muss ich schreiben ;)

Luckie 16. Feb 2015 22:03

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Kann es sein, dass der API Aufruf blockierend ist? Also erst zurückkehrt, wenn er ausgeführt wurde?

Dejan Vu 17. Feb 2015 08:18

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Imho haben die Indies nur in Threads etwas zu suchen. Weiterhin ist das 'Connected' Event nicht der richtige Ort, um eine Schleife zu implementieren.

Soweit ich mich erinnere, hebeln die Indies das Eventkonzept der TCP-Behandlung von Windows komplett aus. Diese ist -zugegebenermaßen- nicht so einfach umzusetzen, weil man doch anders denken muss. Die Indies versuchen also, dem normalen Programmierer entgegenzukommen. Dazu gehört die synchrone Arbeit. Ich lese....ich verarbeite...ich schreibe....
Wärend ich lese oder schreibe, hängt das System. Es gibt zwar eine Komponente, (AntiFreeze), die das hängen verhindern soll, aber das ist ja auch nicht das richtige.

Ich würde also die ganze Abarbeitung in einen Thread verlagern und mit (synchronisierten) Events arbeiten. Während im Hintergrund I/O (blockierend oder nicht: wurscht) läuft, kann ich im Vordergrund weiter mit meiner UI arbeiten. Ein Abbruch-Button würde den Thread dann signalisieren, das -sofern er das zulässt- Feierabend gemacht werden darf.

HL92 24. Feb 2015 13:31

AW: Laufende whileschleife auf Knopfdruck unterbrechen
 
Vielen Dank nochmal für eure Hilfe. Ich habe das Problem nun mit Threads gelöst und es
läuft wunderbar. :thumb: Tolle Hilfe


Alle Zeitangaben in WEZ +1. Es ist jetzt 22:02 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz