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