Delphi

Scheduler di attività in Delphi: thread anonimi e festività

calendar_today personTeam EGSOFT schedule5 min di lettura
info

Disclaimer: le informazioni pubblicate in questa sezione hanno finalità di pura divulgazione tecnica. EG Software S.r.l. non si assume alcuna responsabilità per un utilizzo improprio dei contenuti, né per eventuali danni diretti o indiretti derivanti dalla loro applicazione, e non garantisce l'aggiornamento, l'accuratezza o la completezza delle informazioni riportate. Prima di utilizzare in produzione codice o procedure qui descritte, verificane sempre l'adeguatezza al proprio contesto.

Molti programmi gestionali hanno bisogno di eseguire operazioni ricorrenti — un invio email, un backup, una sincronizzazione — a un orario prestabilito, magari escludendo alcuni giorni festivi. Costruire questa logica come componente Delphi, incapsulato in un TThread anonimo, permette di riutilizzarla ovunque senza duplicare codice di temporizzazione.

Un componente basato su thread anonimo

Il cuore del componente è un record con i dati del task (descrizione, orario, giorni da escludere, nome di una DLL da eseguire) e una classe TComponent che avvia un thread con TThread.CreateAnonymousThread:

type
  TDatiTask = record
    Descrizione: string;
    OrarioEsecuzione: TTime;
    GiorniEsclusione: string;   // Es: 'D:25/12;D:NATALE;D:PASQUA'
    NomeDLL: string;
  end;

  TTaskSchedulerThread = class(TComponent)
  private
    FThread: TThread;
    FDatiTask: TDatiTask;
    FProssimaEsecuzione: TDateTime;
    FAttivo: Boolean;
    procedure Esegui;
    procedure AggiornaProssimaEsecuzione;
    function DeveEseguireOggi: Boolean;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Avvia;
    procedure Ferma;
    property DatiTask: TDatiTask read FDatiTask write FDatiTask;
  end;

implementation

procedure TTaskSchedulerThread.Avvia;
begin
  if not FAttivo then
  begin
    FAttivo := True;
    AggiornaProssimaEsecuzione;
    FThread := TThread.CreateAnonymousThread(Esegui);
    FThread.FreeOnTerminate := False;
    FThread.Start;
  end;
end;

procedure TTaskSchedulerThread.Ferma;
begin
  if FAttivo then
  begin
    FAttivo := False;
    FThread.Terminate;
    FThread.WaitFor;
    FreeAndNil(FThread);
  end;
end;

procedure TTaskSchedulerThread.Esegui;
begin
  while FAttivo do
  begin
    if (Now >= FProssimaEsecuzione) and DeveEseguireOggi then
    begin
      // ... esecuzione del task ...
      AggiornaProssimaEsecuzione;
    end;
    Sleep(1000);
  end;
end;

Il ciclo dentro Esegui gira ogni secondo su un thread separato dalla UI: è un approccio semplice, meno preciso di un timer di sistema ma sufficiente per attività con cadenza giornaliera, e facile da fermare in modo pulito con Terminate + WaitFor nel distruttore.

Escludere le festività, calcolo della Pasqua incluso

Un dettaglio interessante è la gestione delle esclusioni: oltre alle date fisse, il task può essere marcato per saltare le festività "mobili", calcolando la Pasqua con un classico algoritmo (Gauss):

function DataPasqua(AAnno: Integer): TDateTime;
var
  G, C, H, I, J, L: Integer;
  Mese, Giorno: Word;
begin
  G := AAnno mod 19;
  C := AAnno div 100;
  H := (C - C div 4 - (8 * C + 13) div 25 + 19 * G + 15) mod 30;
  I := H - (H div 28) * (1 - (H div 28) * (29 div (H + 1)) * ((21 - G) div 11));
  J := (AAnno + AAnno div 4 + I + 2 - C + C div 4) mod 7;
  L := I - J;
  Mese := 3 + (L + 40) div 44;
  Giorno := L + 28 - 31 * (Mese div 4);
  Result := EncodeDate(AAnno, Mese, Giorno);
end;

Con la data di Pasqua calcolata, la stringa di esclusione può contenere alias simbolici come PASQUA, PASQUETTA, NATALE o FERRAGOSTO, oltre a date fisse in formato gg/mm: un piccolo motore di regole di calendario che evita di dover aggiornare a mano l'elenco delle festività ogni anno.

Eseguire codice esterno da una DLL

Il task vero e proprio, nell'esempio originale, viene eseguito caricando dinamicamente una DLL e richiamando una funzione con una firma concordata:

type
  TFunzioneEsegui = function(Messaggio, Nome: PChar): Boolean; stdcall;

procedure TTaskSchedulerThread.EseguiTask;
var
  HandleDLL: THandle;
  FunzioneEsegui: TFunzioneEsegui;
begin
  HandleDLL := LoadLibrary(PChar(FDatiTask.NomeDLL));
  if HandleDLL = 0 then
    Exit; // Log dell'errore di caricamento

  try
    @FunzioneEsegui := GetProcAddress(HandleDLL, 'Esegui');
    if Assigned(FunzioneEsegui) then
      FunzioneEsegui(PChar('{}'), PChar('Scheduler'));
  finally
    FreeLibrary(HandleDLL);
  end;
end;

Questo pattern a plugin permette di aggiungere nuovi task senza ricompilare l'eseguibile principale: basta distribuire una nuova DLL con la funzione Esegui esportata. L'unica accortezza, dato che il codice gira su un thread separato, è aggiornare log o interfaccia sempre tramite TThread.Queue o TThread.Synchronize, mai toccando direttamente i controlli VCL dal thread secondario.

Altri articoli su Delphi grid_viewTutte le tematiche
Scrivici su WhatsApp