![]() |
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:
Wenn ich das Formular schließe, möchte ich das gleiche machen + auch warten, bis der Task sicher beendet ist:
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;
Code:
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.
procedure TForm1.FormDestroy(Sender: TObject);
begin if Assigned(fTask) then begin fTask.Cancel; while Button1.Enabled = false do; end; end; Was übersehe ich da? |
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. |
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. |
AW: TTask/ITask + Synchronze + OnDestroy
Mit der while-Schleife blockierst du den Main-Thread und das Synchronize kommt nicht zum Zug.
|
AW: TTask/ITask + Synchronze + OnDestroy
Probier doch mal statt Synchronize ein Queue.
|
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. |
AW: TTask/ITask + Synchronze + OnDestroy
Wenn ich das so mache:
Code:
tut das, wie ich will.
while Button1.Enabled = false do begin
Application.ProcessMessages; end; |
AW: TTask/ITask + Synchronze + OnDestroy
Schau Dir mal
![]() an, da wird beschrieben, wie man auf das Ende wartet |
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. |
AW: TTask/ITask + Synchronze + OnDestroy
Ich glaube die Lösung für dich ist vom Formular die Funktion
Code:
und
FormCloseQuery
Code:
Wenn du im Synchronize auf den Button1 zugreifst muss dieser zu dem Zeitpunkt auch noch existieren, ansonsten gibt es eine Zugriffsverletzung.
Task.Wait
|
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. |
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; |
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. |
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
|
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
Daher die Anforderung, zu warten, bis der Thread SICHER beendet ist. |
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.
|
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
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:
Da weiß ich gar nicht, wo ich da ansetzen soll.
while Button1.Enabled = false do begin
Application.ProcessMessages; end; 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: |
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...
|
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
Warum sollte hier ein Thread tauglicher sein als ein Task? |
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
|
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
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. |
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... |
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
|
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. |
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
|
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
|
AW: TTask/ITask + Synchronze + OnDestroy
Zitat:
|
AW: TTask/ITask + Synchronze + OnDestroy
Mit einem Timer geht es:
Code:
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.
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; Besser? |
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:
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:
procedure TvTaskWrapper.FormClose(Sender: TObject; var Action: TCloseAction);
begin Action := aTaskWrapper.CancelTaskWithWait; end;
Code:
Auch das Syncen mit dem UI-Thread und einen Status, damit man weiß, wann der Thread fertig ist, kann man reinpacken:
function TTaskWrapper.CancelTaskWithWait: TCloseAction;
begin if isActive then begin CancelTask; fTimer.Enabled := true; Result := TCloseAction.caNone; end else begin Result := TCloseAction.caFree; end; end;
Code:
Demo attached. Ach ja: Das ist ein proof-of-concept + ist offen für Verbesserung.
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; |
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 17:28 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-2025 by Thomas Breitkreuz