Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   TTask/ITask + Synchronze + OnDestroy (https://www.delphipraxis.net/206572-ttask-itask-synchronze-ondestroy.html)

TigerLilly 7. Jan 2021 16:00

TTask/ITask + Synchronze + OnDestroy
 
Liste der Anhänge anzeigen (Anzahl: 1)
Im attachten Beipielprogramm startet ein Buttpon einen Task. Der zweite Button beendet ihn. Soweit so gut.


Code:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Button1.Enabled := false;
  Button1.Text := 'Task running';
  fTask := TTask.Run(procedure()
    begin
      repeat
        if TTask.CurrentTask.Status = TTaskStatus.Canceled then
          break;
        Sleep(10);
        TThread.Synchronize(nil, procedure()
          begin
            Button1.Text := DateTimeToStr(Now);
          end);
      until false;
      Sleep(100);
      Button1.Enabled := true;
      Button1.Text := 'Start Task';
    end);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if Assigned(fTask) then begin
    fTask.Cancel;
  end;
end;
Wenn ich das Formular schließe, möchte ich das gleiche machen + auch warten, bis der Task sicher beendet ist:

Code:
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(fTask) then begin
    fTask.Cancel;
    while Button1.Enabled = false do;
  end;
end;
Das funktioniert aber nicht, das programm hängt irgendwo im Nirwana. Nimmt man den teil im Task mit TThread.Synchronize raus, funktioniert das wie gewünscht.

Was übersehe ich da?

Bernhard Geyer 7. Jan 2021 16:03

AW: TTask/ITask + Synchronze + OnDestroy
 
FormDestroy ist zu spät.
Da ist ja alles schon am Abräumen und ein Synchronize dürfte schon fast sicher scheitern.

TigerLilly 7. Jan 2021 16:07

AW: TTask/ITask + Synchronze + OnDestroy
 
Ja, das war auch meine erste Vermutung, aber:
OnDestroy wird doch aufgerufen BEVOR alles abgebaut wird.

Und:
Im OnClose gibt es dasselbe Verhalten.

Uwe Raabe 7. Jan 2021 16:28

AW: TTask/ITask + Synchronze + OnDestroy
 
Mit der while-Schleife blockierst du den Main-Thread und das Synchronize kommt nicht zum Zug.

Uwe Raabe 7. Jan 2021 16:31

AW: TTask/ITask + Synchronze + OnDestroy
 
Probier doch mal statt Synchronize ein Queue.

TigerLilly 7. Jan 2021 17:57

AW: TTask/ITask + Synchronze + OnDestroy
 
Queue ist deswegen schwierig, weil das Timing dann noch kritischer wird - ev. sind da dann wirklich schon Sachen abgeräumt.

Ich verstehe, dass die while-Schleife, den Thread blockiert. Und deswegen wartet das programm beim Synchronize + hängt? Ah, ok!
Aber eigentlich sollte das Synchronize gar nicht mehr aufgerufen werden, wenn der Task gecancelt ist.

Aber wie warte ich dann auf das Ende des Tasks?
Ein Workaropund derzeit ist, im OnCloseQuery zu schauen, ob der Task läuft + das Schließen solange zu verhindern, bis der Task beendet oder manuell abgebrochen wurde.

TigerLilly 7. Jan 2021 18:51

AW: TTask/ITask + Synchronze + OnDestroy
 
Wenn ich das so mache:
Code:
    while Button1.Enabled = false do begin
     Application.ProcessMessages;
    end;
tut das, wie ich will.

sh17 7. Jan 2021 19:41

AW: TTask/ITask + Synchronze + OnDestroy
 
Schau Dir mal

http://docwiki.embarcadero.com/RADSt...amming_Library

an, da wird beschrieben, wie man auf das Ende wartet

TigerLilly 7. Jan 2021 21:13

AW: TTask/ITask + Synchronze + OnDestroy
 
Doku kenne ich, danke. Aber das hilft ja nicht.

Meine Erkenntnisse bisher:
Task.Cancel + Task.Wait geht nicht, weil das eine Exceptioin wirft. Task.Wait wartet auf das "normale" Ende des Tasks.
Das Problem ist auch nicht der Task, sondern das Synchronize im Task.
Wait + Synchronize im Task blockiert sich auch. Also ich kann auch nicht einfach warten, bis der Task fertig ist.

Und nochmal: Es geht nur darum, dass das Formular geschlossen wird + allfälige Tasks daher beendet werden müssen (oder gewartet wird, bis sie fertig sind).

Wie gesagt, Lösung bisher: Application.ProcessMessages.

XXcD 7. Jan 2021 23:55

AW: TTask/ITask + Synchronze + OnDestroy
 
Ich glaube die Lösung für dich ist vom Formular die Funktion
Code:
FormCloseQuery
und
Code:
Task.Wait
Wenn du im Synchronize auf den Button1 zugreifst muss dieser zu dem Zeitpunkt auch noch existieren, ansonsten gibt es eine Zugriffsverletzung.

TigerLilly 8. Jan 2021 07:01

AW: TTask/ITask + Synchronze + OnDestroy
 
Nein. Das Synchronize stellt ja eben sicher, dass der Button noch existiert. Wäre bei Thread.Queue anders.

Und ich möchte den Task ja canceln, da hilft wait nicht.

shebang 8. Jan 2021 08:13

AW: TTask/ITask + Synchronze + OnDestroy
 
Damit funktioniert es bei mir:
Delphi-Quellcode:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Assigned(fTask) then begin
    fTask.Cancel;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Button1.Enabled := false;
  Button1.Text := 'Task running';
  fTask := TTask.Run(procedure()
    begin
      repeat
        if TTask.CurrentTask.Status = TTaskStatus.Canceled then
          Exit; // Exit statt Break
        Sleep(10);
        TThread.Synchronize(nil, procedure()
        begin
          Button1.Text := DateTimeToStr(Now);
        end);
      until false;
      Sleep(100);
      TThread.Synchronize(nil, procedure()
      begin
        Button1.Enabled := true;// Diese beiden Zugriffe müssen
        Button1.Text := 'Start Task'; // auch synchronisiert werden
      end);
    end);
end;

TiGü 8. Jan 2021 09:06

AW: TTask/ITask + Synchronze + OnDestroy
 
Ich habe mir das mal umformatiert mit TProc und TThreadProcedure Variablen, weil ich diese durchaus valide Schreibweise mit den geschachtelten anonymen Funktionen nicht so mag.
Nach dem FormDestroy ist das Formular und seine Komponenten schon weg. Daher die Prüfung mit Assigned(Button1), ob der Button überhaupt noch valide da ist.

Delphi-Quellcode:
unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls,
  System.Threading;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    fTask: ITask;
    procedure DoCancelTask;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DoCancelTask;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MainTaskProc: TProc;
begin
  Button1.Enabled := false;
  Button1.Text := 'Task running';

  MainTaskProc := procedure()
    var
      UpdateDateTime: TThreadProcedure;
      ReactivateButton: TThreadProcedure;
    begin
      UpdateDateTime := procedure()
        begin
          if Assigned(Button1) then
          begin
            Button1.Text := DateTimeToStr(Now);
          end;
        end;

      ReactivateButton := procedure()
        begin
          if Assigned(Button1) then
          begin
            Button1.Enabled := true;
            Button1.Text := 'Start Task';
          end;
        end;

      repeat
        if TTask.CurrentTask.Status = TTaskStatus.Canceled then
        begin
          Break;
        end;
        Sleep(10);
        TThread.Synchronize(nil, UpdateDateTime);
      until false;

      Sleep(100);

      TThread.Synchronize(nil, ReactivateButton);
    end;

  fTask := TTask.Run(MainTaskProc);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  DoCancelTask;
end;

procedure TForm1.DoCancelTask;
begin
  if Assigned(fTask) then
  begin
    fTask.Cancel;
  end;
end;

end.

Uwe Raabe 8. Jan 2021 09:29

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von TiGü (Beitrag 1480539)
Nach dem FormDestroy ist das Formular und seine Komponenten schon weg. Daher die Prüfung mit Assigned(Button1), ob der Button überhaupt noch valide da ist.

Wenn das Form und seine Komponenten schon weg sind (ich vermute, das heißt freigegeben), dann kann Assigned(Button1) immer noch True liefern, obwohl der Zugriff darauf dann auf undefiniertem Speicher erfolgt. Das kann dann gut gehen - muss aber nicht.

TigerLilly 8. Jan 2021 09:44

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von shebang (Beitrag 1480537)
Damit funktioniert es bei mir:
Delphi-Quellcode:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Assigned(fTask) then begin
    fTask.Cancel;
  end;
end;

Nein, das funktioniert nur, wenn der Task schnell genug beendet. Wenn das länger dauert, wird das Formular geschlossen und der TAsk läuft noch - weil er mit Beenden noch nicht fertig ist.

Daher die Anforderung, zu warten, bis der Thread SICHER beendet ist.

shebang 8. Jan 2021 10:33

AW: TTask/ITask + Synchronze + OnDestroy
 
Dann wäre die Frage, ob ein Task für deine aktuelle Aufgabe geeignet ist. Wenn ich mir die verfügbaren Methoden von TTask so anschaue, dann sieht es für mich eher so aus, dass Tasks für kurze kleine Aufgaben gedacht sind, auf deren Erfüllung man wartet. Dein Beispiel enthält ja eine Schleife, die abgebrochen werden muss, es gibt kein reguläres Ende. Ich würde an dieser Stelle wohl eher einen Thread nehmen.

TiGü 8. Jan 2021 10:40

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1480541)
Zitat:

Zitat von TiGü (Beitrag 1480539)
Nach dem FormDestroy ist das Formular und seine Komponenten schon weg. Daher die Prüfung mit Assigned(Button1), ob der Button überhaupt noch valide da ist.

Wenn das Form und seine Komponenten schon weg sind (ich vermute, das heißt freigegeben), dann kann Assigned(Button1) immer noch True liefern, obwohl der Zugriff darauf dann auf undefiniertem Speicher erfolgt. Das kann dann gut gehen - muss aber nicht.

Vollkommen richtig.
Zumindest hat das jetzt dreimal anhand dieses Minimalbeispiels funktioniert.
Meine Hand würde ich dafür aber nicht ins Feuer legen.

Zumindest ist das besser als das Rumgehampel mit
Delphi-Quellcode:
while Button1.Enabled = false do begin
     Application.ProcessMessages;
end;
Da weiß ich gar nicht, wo ich da ansetzen soll.

Je nach echter Anforderung würde ich ggf. den Schreib-Overhead mit einem echten TThread-Abkömmling vorschlagen, da man dann die volle Kontrolle hat und gescheit mit einem TEvent warten kann.

Übrigens wundere ich mich, dass noch keiner gemeckert hat, weil im Ursprungspost einfach mal hart im externen Threadkontext auf Eigenschaften der GUI-Komponenten zugegriffen wird (Button.Enabled/Text).
Das steht zwar alles visuell in der TForm1.Button1Click, läuft aber sozusagen im Execute eines externen Threads.
Da wurden andere sonst immer virtuell an den Ohren gezogen. :twisted:

Uwe Raabe 8. Jan 2021 12:01

AW: TTask/ITask + Synchronze + OnDestroy
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich weiß ja nicht, ob das für den realen Anwendungsfall passt, aber in solchen Fällen verwende ich dann ein Zwischenobjekt. Ich hab da mal was vorbereitet...

TigerLilly 8. Jan 2021 12:03

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von shebang (Beitrag 1480557)
Dann wäre die Frage, ob ein Task für deine aktuelle Aufgabe geeignet ist. Wenn ich mir die verfügbaren Methoden von TTask so anschaue, dann sieht es für mich eher so aus, dass Tasks für kurze kleine Aufgaben gedacht sind, auf deren Erfüllung man wartet. Dein Beispiel enthält ja eine Schleife, die abgebrochen werden muss, es gibt kein reguläres Ende. Ich würde an dieser Stelle wohl eher einen Thread nehmen.

Das erschließt sich mir nicht. TTask kapselt doch nur den Zugriff auf Threads. Die Endlos-Schleife samt Abbruch ist nur zu Testzwecken, ändert an der Aufgabenstellung nichts. In echt beendet der Task natürlich, wenn man ihn lässt.

Warum sollte hier ein Thread tauglicher sein als ein Task?

TigerLilly 8. Jan 2021 12:07

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von TiGü (Beitrag 1480559)
Je nach echter Anforderung würde ich ggf. den Schreib-Overhead mit einem echten TThread-Abkömmling vorschlagen, da man dann die volle Kontrolle hat und gescheit mit einem TEvent warten kann.

Nochmal. Ich möchte das Fenster schliueßen + allfällige Tasks dabei beenden. Nur dann(!!) habe ich ein Problem. Das "warten" ist ja leicht gesagt - aber wie ist die Frage. Zeig mir was besseres als mein Rumgehample.

TigerLilly 8. Jan 2021 12:16

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1480568)
Ich weiß ja nicht, ob das für den realen Anwendungsfall passt, aber in solchen Fällen verwende ich dann ein Zwischenobjekt. Ich hab da mal was vorbereitet...

Danke dafür. Aber wenn ich den Task starte und das Fenster schließe, gibt es eine Zugriffsverletzung.Übersehe ich was?

Die Zugriffsverletzung sieht man aber nur im Debugger und nur wenn dias Unterbrechen bei Exceptions eingeschalten ist. Zur Laufzeit wird die Exception nicht angezeigt.

Gilt auch für TiGüs Vorschlag mit OnCloseQuery. Wahrscheinlich wird die Excpetion geworfen, wenn das ganze Exceptionhandling schon abgebaut ist.

Uwe Raabe 8. Jan 2021 12:30

AW: TTask/ITask + Synchronze + OnDestroy
 
Nein, aber ich. Wenn das Form freigegeben wird, dann auch die Wrapper-Instanz - auch wenn die Task noch nicht fertig ist. In meinen Anwendungsfällen hält die Task indirekt selbst noch ein anderes Interface auf den Wrapper, aber dabei geht es auch um eine ganz andere Anforderung.

Multi-Threading bleibt halt schwierig...

TigerLilly 8. Jan 2021 12:38

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1480576)
Multi-Threading bleibt halt schwierig...

Ha! Und ich hab gedacht, ich bin unfähig. Danke trotzdem!

Uwe Raabe 8. Jan 2021 12:51

AW: TTask/ITask + Synchronze + OnDestroy
 
Liste der Anhänge anzeigen (Anzahl: 1)
Neuer Versuch, der sich mehr an meinem (bislang jedenfalls funktionierenden) Code orientiert.
Ich vermute, das eigentliche Problem ist das Halten des ITask-Interfaces, was ich in meinem Code eigentlich nie mache.

shebang 8. Jan 2021 13:24

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von TigerLilly (Beitrag 1480570)
Warum sollte hier ein Thread tauglicher sein als ein Task?

Einem Thread kann ich (über Terminate) mitteilen, dass er sich beenden soll und dann darauf warten, bis er sich selbst beendet hat. Bei einem Task scheint das nicht vorgesehen zu sein.

Uwe Raabe 8. Jan 2021 14:28

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von shebang (Beitrag 1480581)
Einem Thread kann ich (über Terminate) mitteilen, dass er sich beenden soll und dann darauf warten, bis er sich selbst beendet hat. Bei einem Task scheint das nicht vorgesehen zu sein.

Das geht mit ITask auch. Problematisch ist hier nur der Synchronize-Aufruf im Task-Thread, der mit dem Wait im Hauptthread zu einem Deadlock führt. Ohne geeignete Maßnahmen wäre das mit einem TThread auch so.

shebang 8. Jan 2021 15:41

AW: TTask/ITask + Synchronze + OnDestroy
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1480595)
Problematisch ist hier nur der Synchronize-Aufruf im Task-Thread, der mit dem Wait im Hauptthread zu einem Deadlock führt. Ohne geeignete Maßnahmen wäre das mit einem TThread auch so.

Stimmt, der Task müsste selbst unterscheiden, ob er durch den Button oder über das Schließen der Form beendet wurde. Nur im ersten Fall ist eine weitere Synchronisation notwendig.

TigerLilly 8. Jan 2021 15:57

AW: TTask/ITask + Synchronze + OnDestroy
 
Mit einem Timer geht es:

Code:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := true;
  if Assigned(fTask) then begin
    if fTask.Status = TTaskStatus.Running then
      fTask.Cancel;
    Timer1.Enabled := true;
    CanClose:=Button1.Enabled;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if Button1.Enabled then
    Close;
end;
Wenn das Formular geschlossen werden soll, wird der Task gecancelt. Ist der Task aber nocht fertig beendet, wird das Schließen unterbunden + statt dessen ein Timer gestartet, der 1x/sec nachschaut, ob der Task jetzt endlich fertig ist. Wenn ja, wird das Formular geschlossen.

Besser?

TigerLilly 11. Jan 2021 07:46

AW: TTask/ITask + Synchronze + OnDestroy
 
Liste der Anhänge anzeigen (Anzahl: 1)
Uwe hat mich auf die Idee eines Wrappers gebracht, in den man diesen Overhead verpacken kann. Dann schaut das Beenden - mit Warten bis der Task fertig abgebrochen ist, so aus:
Code:
procedure TvTaskWrapper.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := aTaskWrapper.CancelTaskWithWait;
end;
Und das tut nicht viel mehr, als zu schauen, ob der Task fertig ist, wenn nicht wird ein Timer gestertet und das Schließen unterbunden. Der Timmer versucht dann immer wieder, das Close aufzurufen + das Spiel beginnt von vorn:
Code:
function TTaskWrapper.CancelTaskWithWait: TCloseAction;
begin
  if isActive then begin
    CancelTask;
    fTimer.Enabled := true;
    Result := TCloseAction.caNone;
  end else begin
    Result := TCloseAction.caFree;
  end;
end;
Auch das Syncen mit dem UI-Thread und einen Status, damit man weiß, wann der Thread fertig ist, kann man reinpacken:
Code:
type
  TTaskWrapper = class
  private
    fTimer: TTimer;
    fTask: ITask;
    fForm: Tform;
    fisActive: Boolean;
    procedure fOnTimer(Sender: TObject);
    function ShouldStop: Boolean;
  public
    constructor create(aForm: Tform; aProc: TProc);
    destructor Destroy;
    procedure DoSynchronized(aProc: TProc);
    procedure CancelTask;
    function CancelTaskWithWait: TCloseAction;
    property isActive: Boolean read FisActive write FisActive;
  end;
Demo attached. Ach ja: Das ist ein proof-of-concept + ist offen für Verbesserung.

Jumpy 11. Jan 2021 08:06

AW: TTask/ITask + Synchronze + OnDestroy
 
Ich kenn mich mit Threads/Tasks nicht so aus, aber nur mal auf diesen Fall bezogen:

Könnte man den Thread nicht so bauen, dass er statt selber auf die GUI zuzugreifen eine Art Callback anbietet (den er ggf. synchronized abfackelt). Dann könnte man den Callback nämlich detachen, bevor man den Thread beendet. Denn warum soll der Thread noch die GUI updaten, wenn das Fenster eh gererade geschlossen werden soll.
Geht natürlich nur, wenn auf Threads von Außen nachträglich zugegriffen werden kann (wie gesagt, kenn mich damit nicht aus).


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