Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Ansatz für Task-Queue Sequenz (https://www.delphipraxis.net/185502-ansatz-fuer-task-queue-sequenz.html)

Rollo62 17. Jun 2015 10:55

Ansatz für Task-Queue Sequenz
 
Hallo zusammen,

für die Separierung von Aufgaben in Taksk möchte ich anonyme Methoden benutzen, und diese dann mit
kleinen Delays hintereinander ausführen.
Ich möchte einfach eine leicht zu handhabende Funktion um innerhalb von Actions und Events eine
Ausführung "Ohne externen Timer" zu starten, wenn man noch innerhalb der eigentlichen EventFunktion ist.

Ein Beispiel wäre FireMonkey TabControl -> OnTabChange. Wenn in der OnTabChange ein weiterer
TabChange ausgelöst werden soll wird das ignoriert, weil das nach Verlassen des EventFunktion wieder rückgesetzt wird.
Also müsste ich EventFunktion erst beenden lassen, und dann von ausserhalb den TabChange machen den ich möchte.


Ich habe einen ersten Versuch mit TTask und TTread.Synchronize gemacht, siehe hier:

Code:
// Procedure sequence of syncronized Tasks
// iDelay determines a delay before or after the Task, to separate external Events
// -iDelay means Delay before each Task, already before first
// +iDelay means Delay after each Task, the first is w/o delay
procedure Task_Sequence_SyncAndDelayed(iDelay : Integer; const aProcs : TS4Proc_Delegate_Array);
begin

   TTask.Create(procedure ()
                var
                  I  : Integer;
                  tsk : ITask;
                begin


                   for I := 0 to Length(aProcs)-1 do
                   begin

                     tsk := TTask.Create(procedure ()
                                  begin
                                     if iDelay < 0 then
                                       Sleep(Abs(iDelay)); // xx milliseconds

                                     TThread.Synchronize(nil, TThreadProcedure(aProcs[I]) ); //Interact with UI

                                     if iDelay > 0 then
                                       Sleep(Abs(iDelay)); // xx milliseconds

                                  end).Start;

                     tsk.Wait();

                   end; // End loop

                end).Start;

end;

Aufgerufen werden soll sas dann wie folgt: Es können mehrere Aufgaben hintereinandergeschaltet werden, falls nötig.

Code:
        Task_Sequence_SyncAndDelayed(-500,
                                       [
                                          // 1st Task
                                          procedure
                                          begin
                                            //Interact with UI
                                            FFrmNotebook.ClearHeader( True );
                                            ChangeTabActionViewNotebook.ExecuteTarget(ChangeTabActionViewNotebook);
                                          end,
                                          // 2nd Task, separated by Delay
                                          procedure
                                          begin
                                            //Interact with UI
                                            ChangeTabActionViewProject.ExecuteTarget(ChangeTabActionViewProject);
                                          end
                                       ]);

Das scheint so zu funktionieren, aber ich bin nicht sicher ob es auch grundsätzlich in Ordnung ist das so zu machen.
Womöglich gibt es Probleme mit Delphi, Firemonkey, iOS, MAXOS, Android, etc.
Vielleicht gibt es ja auch schon eine Task-Queue die durch Delays getrennt wird, welche ich nicht gefunden habe.

Wäre schön eure Meinung dazu zu bekommen.

Rollo

Mavarik 17. Jun 2015 11:16

AW: Ansatz für Task-Queue Sequenz
 
Falscher Ansatz - würde ich sagen -

Das ist ein klassischer Ansatz für Sir Rufo's Idleworker...

Hole dir

Delphi-Quellcode:
  TMessageManager.DefaultManager.SubscribeToMessage( TIdleMessage, HandleIdleMessage );
Dann nimm eine TaskListe wie

Delphi-Quellcode:
  FTasks: TQueue<TProc>;
In die Queue geht es mit

Delphi-Quellcode:
procedure TIdleWorker.Execute( Action: TProc );
begin
  FTasks.Enqueue( Action );
end;
und die Verarbeitung geht so:

Delphi-Quellcode:
procedure TIdleWorker.HandleIdleMessage( const Sender: TObject; const m: TMessage );
var
  LTask: TProc;
begin
  if FTasks.Count > 0 then
    begin
      LTask := FTasks.Dequeue( );
      LTask( );
    end;
end;
Ich habe "ALLE" Application.Processmessages aus meiner App entfernt...
Warum waren die da drin?

Beispiel:

Delphi-Quellcode:
Procedure TForm1.Button1Click(Sender : TObject);
begin
  Application.Processmessages;
  MachewasLanges;
end;
Weil Firemonkey sonst NIE die Click oder Select Animation abspielt.. (Farbe setzen usw.)
Daher sieht der User keine Reaktion..

Daher mache ich meine Verarbeitung nur noch im Application.OnIdle -> der ruft die obere Message auf...

Dann sieht es so aus:

Delphi-Quellcode:
Procedure TForm1.Button1Click(Sender : TObject);
begin
  TIdleWorker.default.Execute(Procedure
    begin
      MachewasLanges;
    end;
end;
Bedeutet die App ist sofort wieder im MainThread und kann die ganze UI aufbauen... Und wenn alles fertig ist meine Routine aufrufen...

So habe ich alles umgestellt...

Mavarik

Rollo62 17. Jun 2015 11:24

AW: Ansatz für Task-Queue Sequenz
 
Hallo Mavarik,

dankesehr für die Vorschläge.
Habe mir schon gedacht das es was in Richtung Enqueue geben müsste.

Das mit Application.ProcessMessages sehe ich auch so, deshalb hoffe ich das Delay() entsprechend
sauber in allen OS umgesetzt wird.

Ich möchte aber eben noch ein Delay mit einbauen, um die Ausführungen zu Verzögern, wenn ich statt Delay
einen Performancetimer oder ähnliches nehme mache ich eine Loop die auch viel Rechenzeit für nichts verwendet.

Ist da ProcessMessages nicht das kleinere übel ?

Oder gibt es eine DelayFunktion die ohne ProcessMessages auskommt ?

EDIT:
Übrigens
Zitat:

Application.Processmessages;
MachewasLanges;
Mir geht es eher um die Delays, als um etwas Langes zu machen, dafür würde ich schon eine andere Struktur benutzen.

Rollo

Photoner 17. Jun 2015 11:47

AW: Ansatz für Task-Queue Sequenz
 
Man kann dem IdleWorker noch zwei Arrays verpassen:

Delphi-Quellcode:
FTasks          : T???                  //  0..n
EarliestStartArr : Array of Integer     //  0..n  (GetTickCount+gewünschtes Delay)
WaitAfterExecArr : Array of Integer     //  0..n
Delphi-Quellcode:
procedure TIdleWorker.Execute( Action: TProc ; EarliestStart : Integer; WaitAfterExec : Integer);
begin
  FTasks.Enqueue( Action );
  Setlength(EarliestStartArr,Length(EarliestStartArr )+1);
  EarliestStartArr[High(EarliestStartArr )] := EarliestStart;
  Setlength(WaitAfterExecArr ,Length(WaitAfterExecArr)+1);
  WaitAfterExecArr[High(WaitAfterExecArr)] := WaitAfterExec;
end;
Delphi-Quellcode:
procedure TIdleWorker.HandleIdleMessage( const Sender: TObject; const m: TMessage );
var
  LTask: TProc;
begin
  if FTasks.Count > 0 then
    begin
      LTask := FTasks.Dequeue( );
      while GetTickCount<EarliestStartArr[??] do sleep(1);
      LTask( );
      sleep(WaitAfterExecArr[??])
      // hier noch die Arrays anpassen: x[??] löschen und Längen dementsprechend anpassen
    end;
end;
Nicht getestet, nur im Browser getippt.

Sir Rufo 17. Jun 2015 11:48

AW: Ansatz für Task-Queue Sequenz
 
Du willst also etwas nach einer bestimmten Zeit im MainThread abarbeiten lassen.

Dann nimmt man sich eine Liste und fügt diese Methoden dort ein. Die Liste sortiert dann die Einträge nach den Delay-Werten (bzw. dem Zeitpunkt, ab wann diese Methode ausgeführt werden soll).

Jedes Mal, wenn die Anwendung in den Idle-Zustand kommt, dann führt man die einfach die Methode aus, die jetzt an der Reihe ist.

Der Vorteil dabei ist, dass du damit garantieren kannst, dass diese Methoden auch ausgeführt werden. Bei den Tasks geht das eben nicht (wenn man eine bestimmte Grenze überschreitet). Denn je nach CPU-Anzahl sind immer nur eine begrenzte Anzahl an Worker-Threads aktiv. Und wenn du nun dort einen Haufen Tasks einstellst, die mal eben 5 Minuten warten sollen und dann einen Task, der 10 Sekunden warten soll, dann kann es dir u.U. sogar passieren, dass dein 10 Sekunden Task erst nach ein paar Stunden aufgerufen wird!

Die TPL funktioniert dort gut, wo ich Tasks habe, die auch ihre echte Arbeit aufnehmen, wenn diese gestartet wurden und diese Arbeit auch ein Anfang und Ende hat. Langläufer oder Langschläfer haben in der TPL nichts zu suchen, denn dafür ist diese nicht gebaut.

Sir Rufo 17. Jun 2015 11:50

AW: Ansatz für Task-Queue Sequenz
 
@Photoner

So definitiv nicht, sondern eine Liste aus so einem kleinen Record:
Delphi-Quellcode:
TWaitMethod = record
  Proc : TProc;
  ExecuteAfter : TDateTime;
end;
Eine Queue ist hier nicht mehr möglich, da man die Verarbeitung anhand der Startzeiten steuern möchte.

Photoner 17. Jun 2015 11:53

AW: Ansatz für Task-Queue Sequenz
 
@Sir Rufo

Stimmt. Das würde blockieren.

Wie wäre es damit:

In HandleIdleMessage müsste man erst die EarliestStart Liste durchgehen und die erste Methothe die die Bedingung erfüllt wird ausgeführt. Erfüllt das keine, dann ists kein Problem mehr

Rollo62 17. Jun 2015 11:55

AW: Ansatz für Task-Queue Sequenz
 
Hallo Photoner,

danke für den Vorschlag.
Aber da ist auch ein Sleep() drin.

Wenn ich Mavarik richtig verstehe vermutet er darin ein Application.ProcessMessages, und das möchte er mit Recht verbannen.

Da bleibt wohl nur mit GetTickCount zu selber zu zählen.
Aber ist GetTickCount Platform-Spezifisch ?


@Sir Rufo

Ja, so ungefähr. Es sollte aber auch ein möglichst einfache Syntax sein.
Also am Beispiel ChangeTabAction.ExecuteTarget(), da diese Zeile im Aufrufe Probleme macht möchte ich die etwas verzögern damit es nach
dem Aufrufer ausgeführt wird.
- ist nicht unbedingt etwas Langes
- soll aber kein Syntax-Monster werden
- Wenn möglich auch gleicht mehrere solcher Prags nacheinander schalten können, um z.B. Tabs mehrecht gleiten zu lassen

Rollo

Mavarik 17. Jun 2015 12:01

AW: Ansatz für Task-Queue Sequenz
 
Du willst doch Dein Delay nur deswegen machen, damit das Tabchange usw. noch ausgeführt wird.. oder habe ich das falsch verstanden?

Da bei sowas die UITask beschäftigt ist, wird der onIdle sowieso nicht aufgerufen und warten ganz automatisch...

Der Delay müsste ja processorabhängig sein.

Mavarik

Photoner 17. Jun 2015 12:04

AW: Ansatz für Task-Queue Sequenz
 
Sleep function

https://msdn.microsoft.com/en-us/lib...=vs.85%29.aspx

kernel32.dll

"Suspends the execution of the current thread until the time-out interval elapses."

System.Sysutils gibt noch jede Menge Fktn. her. Stichworte:
  • Now
  • GetTime


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:43 Uhr.
Seite 1 von 2  1 2      

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