AGB  ·  Datenschutz  ·  Impressum  







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

Ansatz für Task-Queue Sequenz

Ein Thema von Rollo62 · begonnen am 17. Jun 2015 · letzter Beitrag vom 7. Jul 2015
Antwort Antwort
Seite 1 von 2  1 2      
Benutzerbild von Mavarik
Mavarik

Registriert seit: 9. Feb 2006
Ort: Stolberg (Rhld)
4.154 Beiträge
 
Delphi 10.3 Rio
 
#1

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 11:16
Falscher Ansatz - würde ich sagen -

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

Hole dir

  TMessageManager.DefaultManager.SubscribeToMessage( TIdleMessage, HandleIdleMessage ); Dann nimm eine TaskListe wie

  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
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.174 Beiträge
 
Delphi 12 Athens
 
#2

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 11:24
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

Geändert von Rollo62 (17. Jun 2015 um 11:28 Uhr)
  Mit Zitat antworten Zitat
Photoner

Registriert seit: 6. Dez 2012
Ort: Nürnberg
103 Beiträge
 
Delphi 10.1 Berlin Starter
 
#3

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 11:47
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.
Chris

Geändert von Photoner (17. Jun 2015 um 11:50 Uhr)
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.174 Beiträge
 
Delphi 12 Athens
 
#4

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 11:55
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
  Mit Zitat antworten Zitat
Benutzerbild von Mavarik
Mavarik

Registriert seit: 9. Feb 2006
Ort: Stolberg (Rhld)
4.154 Beiträge
 
Delphi 10.3 Rio
 
#5

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 12:01
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
  Mit Zitat antworten Zitat
Photoner

Registriert seit: 6. Dez 2012
Ort: Nürnberg
103 Beiträge
 
Delphi 10.1 Berlin Starter
 
#6

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 12:04
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
Chris
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#7

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 12:43
Also hier mal so ein IdleWorker, der dann auch die Delays berücksichtigt (ACHTUNG: So nur für FMX, für die VCL müsste man die IdleMessage selber verschicken)
Delphi-Quellcode:
unit IdleWorker;

interface

uses
  System.Generics.Collections, System.Generics.Defaults,
  System.Messaging, System.SysUtils, System.TimeSpan, System.DateUtils;

type
  TIdleWorker = class
  private const
    MinWorkingTime = 20;
    DefaultWorkingTime = 50;
  type
    TTask = record
      Action: TProc;
      ExecuteAfter: TDateTime;
    end;
  private
    FTasks: TList<TTask>;
    FWorkingTime: Cardinal;
    procedure SetWorkingTime( const Value: Cardinal );
  protected
    procedure HandleIdleMessage( const Sender: TObject; const m: TMessage );
  public
    constructor Create( );
    destructor Destroy; override;

    procedure Execute( Action: TProc ); overload;
    procedure Execute( Action: TProc; ADelay: TTimeSpan ); overload;
    procedure Execute( Action: TProc; ADelay: Cardinal ); overload;
    procedure Execute( Action: TProc; AExecuteAfter: TDateTime ); overload;

    property WorkingTime: Cardinal read FWorkingTime write SetWorkingTime default DefaultWorkingTime;
  private
    class var _Default: TIdleWorker;
  protected
    class destructor Destroy;
  public
    class function Default: TIdleWorker;
  end;

implementation

uses
  System.Diagnostics,
  FMX.Types;

{ TIdleWorker }

constructor TIdleWorker.Create;
begin
  inherited;
  FWorkingTime := DefaultWorkingTime;
  FTasks := TList<TTask>.Create( TComparer<TTask>.Construct(
    function( const L, R: TTask ): integer
    begin
      Result := CompareDateTime( R.ExecuteAfter, L.ExecuteAfter );
    end ) );

  TMessageManager.DefaultManager.SubscribeToMessage( TIdleMessage, HandleIdleMessage );
end;

class function TIdleWorker.Default: TIdleWorker;
begin
  if not Assigned( _Default ) then
    _Default := TIdleWorker.Create( );
  Result := _Default;
end;

class destructor TIdleWorker.Destroy;
begin
  FreeAndNil( _Default );
end;

destructor TIdleWorker.Destroy;
begin
  TMessageManager.DefaultManager.Unsubscribe( TIdleMessage, HandleIdleMessage );
  FTasks.Free;
  inherited;
end;

procedure TIdleWorker.Execute( Action: TProc; ADelay: TTimeSpan );
begin
  Execute( Action, Now + ADelay );
end;

procedure TIdleWorker.Execute( Action: TProc );
begin
  Execute( Action, Now );
end;

procedure TIdleWorker.Execute( Action: TProc; AExecuteAfter: TDateTime );
var
  LTask: TTask;
begin
  LTask.Action := Action;
  LTask.ExecuteAfter := AExecuteAfter;
  FTasks.Add( LTask );
  FTasks.Sort( );
end;

procedure TIdleWorker.HandleIdleMessage( const Sender: TObject; const m: TMessage );
var
  LTask: TTask;
  LSW: TStopwatch;
begin
  LSW := TStopwatch.StartNew( );
  while ( LSW.ElapsedMilliseconds < FWorkingTime ) do
  begin
    if ( FTasks.Count > 0 ) and ( FTasks.Last.ExecuteAfter <= Now ) then
    begin
      LTask := FTasks.Extract( FTasks.Last );
      LTask.Action( );
    end
    else
      Break;
  end;
end;

procedure TIdleWorker.SetWorkingTime( const Value: Cardinal );
begin
  if Value >= MinWorkingTime then
    FWorkingTime := Value;
end;

procedure TIdleWorker.Execute( Action: TProc; ADelay: Cardinal );
begin
  Execute( Action, IncMilliSecond( Now, ADelay ) );
end;

end.
und ein kleiner Testaufruf:
Delphi-Quellcode:
unit Form.Main;

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, FMX.Layouts, FMX.ListBox;

type
  TForm1 = class( TForm )
    ListBox1: TListBox;
    Button1: TButton;
    procedure Button1Click( Sender: TObject );
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  System.TimeSpan, IdleWorker;

procedure TForm1.Button1Click( Sender: TObject );
begin
  TIdleWorker.Default.Execute(
    procedure
    begin
      ListBox1.Items.Add( 'Sofort' );
    end );

  TIdleWorker.Default.Execute(
    procedure
    begin
      ListBox1.Items.Add( 'nach 750 Millisekunden' );
    end, 750 );

  TIdleWorker.Default.Execute(
    procedure
    begin
      ListBox1.Items.Add( 'nach 3 Sekunden' );
    end, TTimeSpan.FromSeconds( 3 ) );
end;

end.
Nachtrag
Das ist die Unit, damit die IdleWorker auch unter VCL läuft
Delphi-Quellcode:
unit IdleWorker.VclBroker;

interface

implementation

uses
  FMX.Types,
  System.Classes, System.Messaging,
  Vcl.Forms, Vcl.AppEvnts;

type
  TVclIdleMessageBroker = class( TComponent )
  private
    FAppEvents: TApplicationEvents;
    procedure AppEventsOnIdle( Sender: TObject; var Done: Boolean );
  public
    procedure AfterConstruction; override;
  end;

  { TVclIdleMessageBroker }

procedure TVclIdleMessageBroker.AfterConstruction;
begin
  inherited;
  FAppEvents := TApplicationEvents.Create( Self );
  FAppEvents.OnIdle := AppEventsOnIdle;
end;

procedure TVclIdleMessageBroker.AppEventsOnIdle( Sender: TObject; var Done: Boolean );
begin
  TMessageManager.DefaultManager.SendMessage( Self, TIdleMessage.Create( ) );
end;

initialization

TVclIdleMessageBroker.Create( Application );

end.
Mit der VCL sieht das Beispiel dann so aus:
Delphi-Quellcode:
unit Form.Main;

interface

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

type
  TMainForm = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

uses
  System.TimeSpan,
  IdleWorker,
  IdleWorker.VclBroker {<- den hier nicht vergessen};

procedure TMainForm.Button1Click(Sender: TObject);
begin
  TIdleWorker.Default.Execute(
    procedure
    begin
      ListBox1.Items.Add( 'Sofort' );
    end );

  TIdleWorker.Default.Execute(
    procedure
    begin
      ListBox1.Items.Add( 'nach 750 Millisekunden' );
    end, 750 );

  TIdleWorker.Default.Execute(
    procedure
    begin
      ListBox1.Items.Add( 'nach 3 Sekunden' );
    end, TTimeSpan.FromSeconds( 3 ) );
end;

end.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo (17. Jun 2015 um 13:01 Uhr)
  Mit Zitat antworten Zitat
Photoner

Registriert seit: 6. Dez 2012
Ort: Nürnberg
103 Beiträge
 
Delphi 10.1 Berlin Starter
 
#8

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 15:39
@Sir Rufo:

Kleinigkeit:
-TMessageManager ist in XE5 in der FMX.Messages. XE6 u. XE7 System.Messaging

Ansonsten Kudos!

TIdleWorker löst Probleme die einen schier zur Verzweiflung bringen.

Bsp.:

Delphi-Quellcode:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
  IdleWorker.TIdleWorker.Default.Execute(
    procedure
    begin
      TEdit(Sender).SelectAll;
    end);
end;
Chris
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#9

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 11:48
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.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#10

AW: Ansatz für Task-Queue Sequenz

  Alt 17. Jun 2015, 11:50
@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.
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  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 23:31 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