8.7. Eventi¶
asyncio.Event è la primitiva di segnalazione più semplice fornita dal modulo. Una coroutine imposta un evento quando è accaduto qualcosa; un numero qualsiasi di altre coroutine attende che l’evento sia impostato prima di continuare. Non c’è alcun payload – un evento è un booleano che si attiva e rimane attivo finché qualcosa non lo azzera.
8.7.1. La forma di base¶
import asyncio
async def waiter(evt):
print("waiting")
await evt.wait()
print("got signal")
async def main():
evt = asyncio.Event()
asyncio.create_task(waiter(evt))
await asyncio.sleep(1)
evt.set()
await asyncio.sleep(0)
asyncio.run(main())
Il task in attesa viene eseguito, raggiunge await evt.wait() e viene sospeso – l’evento parte nello stato azzerato, quindi la chiamata wait cede il controllo al loop. Un secondo dopo main chiama evt.set() e il task in attesa viene pianificato per la ripresa. L”await asyncio.sleep(0) finale è semplicemente un yield, così il loop ha la possibilità di eseguire il task in attesa prima che main ritorni.
8.7.2. I quattro metodi¶
set()– imposta l’evento e pianifica la ripresa di ogni coroutine attualmente bloccata suwait(). L’evento rimane impostato finché non viene azzerato.clear()– riporta l’evento allo stato azzerato. Le coroutine che eseguonoawait wait()in seguito si bloccheranno di nuovo.wait()– una coroutine. Se l’evento è già impostato, ritorna immediatamente. Altrimenti si blocca finché qualcosa non lo imposta.is_set()– restituisce lo stato corrente senza bloccare. Utile quando una coroutine vuole controllare l’evento senza attenderlo.
8.7.3. Più coroutine in attesa¶
Diverse coroutine possono eseguire await sullo stesso evento. Quando qualcosa chiama set() vengono pianificate tutte per la ripresa. L’evento è uno-a-molti per impostazione predefinita; non c’è bisogno di più eventi per diramarsi verso più coroutine in attesa.
8.7.4. Riutilizzare un evento¶
Uno schema comune è usare un evento ripetutamente – una coroutine che viene eseguita una volta per ogni segnale – azzerandolo dopo ogni passaggio:
async def worker(go):
while True:
await go.wait()
do_one_unit_of_work()
go.clear()
Il produttore imposta l’evento ogni volta che c’è lavoro da fare; il worker lo azzera una volta che ha raccolto il segnale. Se il produttore potrebbe impostare di nuovo l’evento prima che il worker si sia svegliato, il worker potrebbe voler gestire quel caso (while go.is_set(): ...) prima di azzerarlo.
8.7.5. Contesti sicuri¶
Su Event non è sicuro chiamare set() dall’interno di un gestore di interrupt. Il meccanismo che usa per pianificare le coroutine in attesa presuppone di essere eseguito all’interno del contesto proprio dell’event loop, cosa che gli interrupt non sono. Per risvegliare un task asyncio da un gestore di interrupt, ThreadSafeFlag (trattato a breve) è la primitiva giusta.