sched - Pianificatore di Eventi Temporizzati

Scopo: Pianificatore di eventi generico

Il modulo sched implementa un pianificatore di eventi generico per eseguire compiti a un orario specifico. La classe per la pianificazione usa una funzione time per ottenere l'orario corrente e una funzione delay per attendere uno specifico periodo di tempo. Le effettive unità temporali non sono importanti, il che rende l'interfaccia sufficientemente flessibile per l'utilizzo per molti scopi.

La funzione time viene chiamata senza argomenti, e dovrebbe ritornare un numero che rappresenta l'orario corrente. La funzione delay viene chiamata con un intero come singolo argomento, usando la stessa scala della funzione time, e dovrebbe attendere quelle unità temporali indicate prima di ritornare. Nella modalità predefinita, sono usate le funzioni monotonic() e sleep dal modulo time, ma gli esempi di questo articolo usano time.time(), anche questa in possesso di tutti i requisiti, poichè facilita la comprensione del risultato.

Per supportare applicazioni a thread multipli, la funzione di attesa viene chiamata con argomento 0 dopo la generazione di ciascun evento, per assicurarsi che gli altri thread abbiano anch'essi una possibilità di essere eseguiti.

Eseguire Eventi con un Periodo di Attesa

Gli eventi possono essere pianificati per una esecuzione dopo un periodo di attesa, oppure a un dato orario. Per pianificarli con un periodo di attesa, si usi enter(), che riceve quattro argomenti:

  • Un numero che rappresenta il periodo di attesa
  • Un valore di priorità
  • La funzione da chiamare
  • Una tupla di argomenti per la funzione

Questo esempio pianifica due eventi differenti da eseguirsi rispettivamente dopo due e tre secondi. Quando scocca l'orario dell'evento, viene chiamato print_event(), che stampa l'orario corrente e il nome dell'argomento passato all'evento.

# sched_basic.py

import sched
import time

scheduler = sched.scheduler(time.time, time.sleep)


def print_event(name, start):
    now = time.time()
    elapsed = int(now - start)
    print('EVENTO: {} trascorso={} nome={}'.format(
        time.ctime(now), elapsed, name))


start = time.time()
print('PARTENZA:', time.ctime(start))
scheduler.enter(2, 1, print_event, ('primo', start))
scheduler.enter(3, 1, print_event, ('secondo', start))

scheduler.run()

L'esecuzione del programma produce:

$ python3 sched_basic.py

PARTENZA: Thu Jun  3 09:15:33 2021
EVENTO: Thu Jun  3 09:15:35 2021 trascorso=2 nome=primo
EVENTO: Thu Jun  3 09:15:36 2021 trascorso=3 nome=secondo

L'orario stampato per il primo evento è di due secondi dopo la partenza, e quello per il secondo è di tre secondi dopo la partenza.

Eventi Sovrapposti

La chiamata di run() blocca fino a quando tutti gli eventi sono stati elaborati. Ogni evento viene eseguito nello stesso thread, quindi se un evento impiega più tempo a eseguirsi rispetto al periodo di attesa impostato tra gli eventi, ci sarà una sovrapposizione. La sovrapposizione viene risolta posticipando l'evento che segue più in là nel tempo. Nessun evento viene perso, ma alcuni eventi potrebbero essere chiamati in ritardo rispetto alla loro pianificazione. Nell'esempio di seguito, long_event() si mette in pausa artificialmente ma la pausa potrebbe altrettanto facilmente essere provocata da una attesa per una lunga operazione di calcolo oppure da un blocco in I/O-.

# sched_overlap.py

import sched
import time

scheduler = sched.scheduler(time.time, time.sleep)


def long_event(name):
    print('INIZIO EVENTO :', time.ctime(time.time()), name)
    time.sleep(2)
    print('FINE EVENTO:', time.ctime(time.time()), name)


print('PARTENZA:', time.ctime(time.time()))
scheduler.enter(2, 1, long_event, ('primo',))
scheduler.enter(3, 1, long_event, ('secondo',))

scheduler.run()

Il risultato è che il secondo evento viene eseguito immediatamente dopo la fine del primo, visto che la durata di esecuzione del primo evento è dilatata oltre l'orario di partenza prefissato per il secondo evento.

$ python3 sched_overlap.py

PARTENZA: Thu Jun  3 09:15:36 2021
INIZIO EVENTO : Thu Jun  3 09:15:38 2021 primo
FINE EVENTO: Thu Jun  3 09:15:40 2021 primo
INIZIO EVENTO : Thu Jun  3 09:15:40 2021 secondo
FINE EVENTO: Thu Jun  3 09:15:42 2021 secondo

Priorità degli Eventi

Se più di un evento è pianificato per lo stesso orario, saranno usati i propri valori di priorità per determinarne l'ordine di esecuzione.

# sched_priority.py

import sched
import time

scheduler = sched.scheduler(time.time, time.sleep)


def print_event(name):
    print('EVENTO:', time.ctime(time.time()), name)


now = time.time()
print('PARTENZA:', time.ctime(now))
scheduler.enterabs(now + 2, 2, print_event, ('primo',))
scheduler.enterabs(now + 2, 1, print_event, ('secondo',))

scheduler.run()

Per questo esempio è necessario assicurarsi che gli eventi siano pianificati per lo stesso preciso orario, quindi viene usato enterabs() in luogo di enter(). Il primo argomento per enterabs() è l'orario di esecuzione dell'evento, invece che il tempo di attesa.

$ python3 sched_priority.py

PARTENZA: Thu Jun  3 09:15:43 2021
EVENTO: Thu Jun  3 09:15:45 2021 secondo
EVENTO: Thu Jun  3 09:15:45 2021 primo

Cancellare Eventi

Sia enter() che enterabs() ritornano un riferimento all'evento, che può essere usato per cancellarlo successivamente. Visto che run() blocca, l'evento deve essere cancellato in un thread diverso. Per questo esempio un thread viene fatto partire per eseguire il pianificatore e il thread principale di elaborazione viene usato per cancellare l'evento.

# sched_cancel.py

import sched
import threading
import time

scheduler = sched.scheduler(time.time, time.sleep)

# Imposta variabile globale che può essere modificata daithread
counter = 0


def increment_counter(name):
    global counter
    print('EVENTO:', time.ctime(time.time()), name)
    counter += 1
    print('ADESSO:', counter)


print('PARTENZA:', time.ctime(time.time()))
e1 = scheduler.enter(2, 1, increment_counter, ('E1',))
e2 = scheduler.enter(3, 1, increment_counter, ('E2',))

# Fa partire un thread per eseguire gli eventi
t = threading.Thread(target=scheduler.run)
t.start()

# Ritorna al thread principale, cancella il primo evento pianificato
scheduler.cancel(e1)

# Attende che il pianificatore termini l'esecuzione nel thread
t.join()

print('FINALE:', counter)

Vengono pianificati due eventi, ma il primo viene successivamente cancellato. Solo il secondo viene eseguito, quindi la variabile contatore viene incrementata una sola volta.

$ python3 sched_cancel.py

PARTENZA: Thu Jun  3 09:15:45 2021
EVENTO: Thu Jun  3 09:15:48 2021 E2
ADESSO: 1
FINALE: 1

Vedere anche:

sched
La documentazione della libreria standard per questo modulo.
time
Il modulo time