8.3. L’event loop

L’event loop è il motore che asyncio esegue al di sotto. Mantiene un elenco di ogni task del programma, chiede a ciascuno di eseguire fino al suo prossimo await e passa al task pronto successivo. Quando non ci sono task pronti, attende – l’attesa effettiva è ciò che rende la CPU disponibile per far eseguire al firmware altre cose e permette agli sleep di risparmio energetico di entrare in azione – finché qualcosa che un task stava attendendo diventa disponibile, quindi riprende quel task. Si ripete all’infinito.

La maggior parte delle applicazioni non interagisce mai direttamente con il loop. Il loop è una conseguenza della chiamata a asyncio.run(); l’applicazione scrive coroutine, le schedula come task, e il loop fa il resto.

8.3.1. Cosa fa effettivamente asyncio.run()

Una singola chiamata:

asyncio.run(main())

è un’abbreviazione per una sequenza più lunga che il loop gestisce per conto dell’applicazione:

  1. Crea l’event loop se non esiste già.

  2. Incapsula la coroutine fornita in un task e la schedula come punto di ingresso di primo livello del loop.

  3. Esegue il loop – scorre i task pronti, attende quando nessuno è pronto, riprende i task quando i loro await si completano – finché il task di primo livello non ritorna o solleva un’eccezione.

  4. Annulla qualsiasi task creato dall’applicazione ancora in esecuzione.

  5. Restituisce qualsiasi cosa la coroutine di primo livello abbia restituito (o rilancia qualsiasi eccezione abbia sollevato).

8.3.2. Un unico loop per programma

L’asyncio di MicroPython ha un solo event loop, punto. Non si può creare un nuovo loop e non si può annidare un loop dentro un altro. Chiamare asyncio.run() dall’interno di una coroutine già in esecuzione sul loop è un errore; il loop è già presente, e la coroutine deve semplicemente eseguire await su qualsiasi cosa volesse avviare.

In pratica la regola è la stessa della riga conclusiva della pagina precedente: c’è esattamente una chiamata a asyncio.run() per programma, in cima, con un singolo async def main() alle spalle. Tutto il resto vive dentro main.

8.3.3. Accesso diretto al loop

Per i rari casi in cui un’applicazione deve accedere al loop stesso – principalmente diagnostica e gestori di eccezioni – asyncio.get_event_loop() restituisce l’oggetto Loop. Da lì l’applicazione può installare un gestore di eccezioni personalizzato, ispezionare cosa sta facendo il loop, oppure (molto occasionalmente) chiamare direttamente create_task() invece di asyncio.create_task() (sono la stessa operazione).

L’insieme completo dei metodi esposti da Looprun_forever(), stop(), set_exception_handler() e gli altri – è trattato nella pagina controllo del loop più avanti in questa sezione. Fino ad allora, asyncio.run(main()) è tutto ciò di cui un’applicazione ha bisogno.