Programación Asíncrona con asyncio#
Introducción#
La programación asíncrona en Python permite ejecutar múltiples tareas de manera concurrente sin bloquear la ejecución del programa. Este modelo es especialmente útil cuando trabajamos con operaciones de entrada/salida (I/O), por ejemplo:
Peticiones HTTP
Bases de datos
Redis
WebSockets
Lectura y escritura de archivos
Servicios web
En aplicaciones modernas, particularmente utilizando frameworks como :contentReference[oaicite:0]{index=0}, la programación asíncrona permite atender miles de conexiones concurrentes utilizando pocos recursos.
La idea principal es simple:
Cuando una tarea está esperando una operación lenta de I/O, el programa puede continuar ejecutando otras tareas.
Funciones asíncronas#
Las funciones asíncronas se definen utilizando la palabra reservada async:
async def hola():
print("Hola")
Estas funciones no se ejecutan inmediatamente. Al invocarlas regresan un objeto especial llamado coroutine.
c = hola()
print(c)
Resultado:
<coroutine object hola at 0x...>
Para ejecutar una coroutine necesitamos un event loop.
El event loop#
El event loop es el mecanismo encargado de coordinar y ejecutar las tareas asíncronas.
La manera moderna de iniciar un event loop en Python es mediante:
import asyncio
asyncio.run(hola())
Internamente sucede algo similar a:
loop = crear_event_loop()
loop.run_until_complete(hola())
loop.close()
La función asyncio.run():
crea el event loop
ejecuta la coroutine principal
mantiene vivo el loop
lo cierra al terminar
La palabra await#
La palabra reservada await indica:
Espera este resultado sin bloquear el programa.
Veamos un ejemplo:
import asyncio
async def hola():
print("Inicio")
await asyncio.sleep(2)
print("Fin")
asyncio.run(hola())
Resultado:
Inicio
(espera 2 segundos)
Fin
Durante la espera de asyncio.sleep(2), el event loop puede ejecutar otras tareas.
Importante#
La instrucción:
await algo()
no significa:
«detener el programa completamente»
sino:
«pausar esta coroutine y ejecutar otras tareas mientras esperamos»
Diferencia entre time.sleep y asyncio.sleep#
Una confusión común consiste en pensar que asyncio.sleep() es equivalente a time.sleep().
No es así.
time.sleep()#
import time
time.sleep(5)
Bloquea completamente el hilo actual.
Durante esos cinco segundos el programa no puede continuar.
asyncio.sleep()#
await asyncio.sleep(5)
Pausa únicamente la coroutine actual y permite que el event loop siga ejecutando otras tareas.
Esto es fundamental para aplicaciones web concurrentes.
Concurrencia#
La programación asíncrona no implica necesariamente paralelismo.
Por ejemplo:
import asyncio
async def hola():
await asyncio.sleep(2)
for i in range(6):
asyncio.run(hola())
Este código tarda aproximadamente doce segundos porque las tareas se ejecutan secuencialmente.
Cada llamada a asyncio.run():
crea un nuevo event loop
ejecuta una sola coroutine
termina el loop
Para ejecutar tareas concurrentemente debemos utilizar múltiples coroutines dentro del mismo event loop.
Ejemplo:
import asyncio
async def hola(i):
print(f"Inicio {i}")
await asyncio.sleep(2)
print(f"Fin {i}")
async def main():
await asyncio.gather(
hola(1),
hola(2),
hola(3),
hola(4),
hola(5),
hola(6)
)
asyncio.run(main())
Ahora el programa tarda aproximadamente dos segundos.
asyncio.gather()#
La función asyncio.gather() permite ejecutar múltiples coroutines concurrentemente.
Ejemplo:
await asyncio.gather(
tarea1(),
tarea2(),
tarea3()
)
Todas las tareas se ejecutan dentro del mismo event loop.
La función espera a que todas terminen.
create_task()#
Otra función importante es asyncio.create_task().
Esta función programa una coroutine para ejecutarse concurrentemente.
Ejemplo:
import asyncio
async def worker():
print("Inicio")
await asyncio.sleep(3)
print("Fin")
async def main():
asyncio.create_task(worker())
print("Main continúa")
await asyncio.sleep(5)
asyncio.run(main())
Resultado:
Main continúa
Inicio
(3 segundos)
Fin
A diferencia de await, create_task() no espera automáticamente el resultado.
La tarea se ejecuta «en segundo plano» dentro del event loop.
Importante#
create_task() devuelve un objeto Task.
task = asyncio.create_task(worker())
Posteriormente podemos esperar su resultado:
await task
Programación asíncrona y operaciones de I/O#
La programación asíncrona es especialmente útil para operaciones lentas de entrada/salida.
Por ejemplo:
consultas Redis
acceso a bases de datos
peticiones HTTP
lectura de archivos
WebSockets
Ejemplo con Redis:
val = await redis.get("archivo")
Aquí:
Python envía una petición a Redis
Redis procesa la solicitud
Mientras llega la respuesta, el event loop ejecuta otras tareas
Cuando Redis responde, la coroutine continúa
Esto permite manejar miles de conexiones concurrentes eficientemente.
Polling y asyncio.sleep()#
Supongamos el siguiente código:
while True:
val = await redis.get(filename)
if val == "ok":
break
Sin una pausa, este ciclo consultaría Redis miles o millones de veces por segundo.
Para evitarlo utilizamos:
await asyncio.sleep(1)
Esto significa:
«espera un segundo antes de volver a consultar»
Además, durante esa espera el event loop puede continuar ejecutando otras tareas.
Importante#
asyncio.sleep() NO espera a Redis.
Redis ya fue esperado aquí:
await redis.get(filename)
La pausa es adicional y voluntaria.
Generadores asíncronos#
Los generadores asíncronos combinan:
awaityield
Ejemplo:
async def generador():
await asyncio.sleep(1)
yield "Hola"
Estos generadores son ideales para:
Server-Sent Events (SSE)
WebSockets
Streaming HTTP
generación incremental de datos
APIs de inteligencia artificial
Ejemplo de SSE con FastAPI#
El siguiente ejemplo implementa un flujo SSE utilizando :contentReference[oaicite:1]{index=1} y :contentReference[oaicite:2]{index=2}.
import asyncio
import json
import redis.asyncio as redis
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
app = FastAPI()
redis_client = redis.from_url(
"redis://localhost:6379",
encoding="utf-8",
decode_responses=True
)
async def event_generator(filename: str):
while True:
val = await redis_client.get(filename)
if val == "ok":
data = json.dumps({
"content": filename
})
yield f"data: {data}\n\n"
break
await asyncio.sleep(1)
@app.get("/events/{filename}")
async def events(request: Request, filename: str):
return StreamingResponse(
event_generator(filename),
media_type="text/event-stream"
)
En este ejemplo:
la coroutine consulta Redis
espera asincrónicamente
produce eventos incrementalmente mediante
yield
Esto permite implementar notificaciones en tiempo real de manera eficiente.
Resumen#
En esta sección vimos los conceptos fundamentales de la programación asíncrona en Python:
async defdefine funciones asíncronasawaitpausa una coroutine sin bloquear el programaasyncio.run()crea y ejecuta el event loopasyncio.gather()ejecuta múltiples coroutines concurrentementeasyncio.create_task()programa tareas concurrentesasyncio.sleep()pausa cooperativamente una coroutinelos generadores asíncronos permiten producir datos incrementalmente
La programación asíncrona es uno de los pilares fundamentales del desarrollo moderno de APIs, servicios web y sistemas distribuidos en Python. ``` .. note:
Contenido creado utilizando asistente ChatGPT.