8.15. Errores comunes¶
Los mismos patrones que hacen agradable a asyncio – sin apropiación, awaits explícitos – le dan su propio conjunto de situaciones que muerden. Esta página es el catálogo de las que surgen con la suficiente frecuencia como para que valga la pena conocerlas.
8.15.1. Olvidar el await¶
Llamar a una función async def devuelve un objeto corrutina. No ejecuta el cuerpo de la función. Para ejecutarlo realmente, la corrutina tiene que ser objeto de await o envolverse en una tarea:
async def main():
send_request() # bug: returns the coroutine, does nothing
await send_request() # right: run it to completion
asyncio.create_task(send_request()) # right: run it concurrently
El error es silencioso – el objeto corrutina se crea, se descarta y nunca se ejecuta. La aplicación continúa como si todo hubiera funcionado. MicroPython a veces registrará una advertencia de que una corrutina nunca fue esperada; otras veces no lo hará. Audita la falta de await en cada punto de llamada que parezca una llamada a función.
8.15.2. Bucles cerrados sin await¶
Una corrutina que se ejecuta en un bucle y nunca hace await monopoliza el bucle de eventos. Ninguna otra tarea avanza hasta que el bucle termina o cede:
async def counter():
n = 0
while True:
n += 1 # bug: starves the loop
La solución es un yield dentro del bucle – normalmente await asyncio.sleep_ms(0) – para que otras tareas listas tengan la oportunidad de ejecutarse. El trabajo intensivo en cómputo también pertenece a esa forma: un bucle de procesamiento de imagen que se ejecuta durante cientos de milisegundos por iteración debería ceder al menos una vez por iteración para que el resto del programa no se atasque.
8.15.3. Tragarse CancelledError¶
La página de cancelación ya cubrió esto en detalle. Lo repetimos aquí porque es la causa más común de «mi aplicación no se apaga»: una corrutina captura asyncio.CancelledError con fines de limpieza y olvida volver a lanzarla. La tarea sigue ejecutándose; el llamador que solicitó la cancelación queda colgado para siempre esperando a que finalice. Vuelve a lanzar siempre tras la limpieza, o usa un bloque try/finally en lugar de un except explícito.
8.15.5. await a nivel de módulo¶
await solo es válido dentro de un cuerpo async def. Escribirlo a nivel de módulo – fuera de cualquier corrutina – es un error de sintaxis:
# bug: not inside an async def
result = await fetch()
La solución es poner el trabajo en una corrutina y llamarla desde el punto de entrada asyncio.run() del programa.
8.15.6. Múltiples llamadas a asyncio.run¶
MicroPython tiene un bucle de eventos. Llamar a asyncio.run() dos veces seguidas – una para la configuración, otra para el trabajo principal – sigue usando el mismo bucle. Llamarlo desde dentro de una corrutina en ejecución es un error: el bucle ya está en marcha. Ambos casos surgen con mayor frecuencia cuando un script crece de forma orgánica y el autor intenta ampliarlo añadiendo más llamadas a run() en lugar de integrar el nuevo trabajo en el main existente.
8.15.7. Uso de Event desde una interrupción¶
asyncio.Event.set() solo es seguro de llamar desde dentro del bucle de eventos. Llamarlo desde un manejador de interrupciones de GPIO es un riesgo de corrupción. Para despertar una tarea desde una interrupción, usa en su lugar ThreadSafeFlag – la página dedicada cubre el patrón.
8.15.8. Llamadas síncronas largas¶
Una corrutina puede esperar las propias primitivas de espera de asyncio; cualquier otra cosa que llame se ejecuta de forma síncrona y bloquea el bucle hasta que retorna. Un time.sleep() bloqueante de 200 ms, una escritura en tarjeta SD que tarda 80 ms en volcarse, una compresión JPEG grande, una llamada a csi.CSI.snapshot() – cada una de ellas retiene el bucle de eventos durante toda su duración. La solución depende de la llamada:
Para
time.sleep: reemplázalo porawait asyncio.sleepoawait asyncio.sleep_ms.Para
csi.CSI.snapshot: usa el envoltorio de captura asíncrona que construye la página de captura.Para cómputo largo (procesamiento de imagen, codificación JPEG): asume el coste o divide el trabajo en fragmentos que hagan
awaitentre iteraciones.
Asyncio no puede convertir una llamada síncrona en no bloqueante. Solo puede dejar que otras corrutinas se ejecuten mientras algo más está esperando.