Hamburger Icon
Asincronía en Python con asyncio

Asincronía en Python con asyncio

Si usas Python, asyncio es una biblioteca para escribir código concurrente utilizando la sintaxis async/await. Es utilizado como base en múltiples frameworks asíncronos y provee un alto rendimiento en redes y servidores web, bibliotecas de conexión de base de datos, colas de tareas distribuidas, etc.

La librería asyncio suele encajar perfectamente para operaciones con límite de E/S y código de red estructurado de alto nivel. Algunos casos de uso serían por ejemplo paralelizar operaciones en un servidor web, en una aplicación de chat o de scraping.

Vamos a empezar con un ejemplo muy básico:

import asyncio

async def main():
    await asyncio.sleep(1)
    print("Hola Mundo")

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
asyncio.run(main())

Vamos a desgranar lo que está sucediendo. Primero se ha definido la función main, que simplemente espera un segundo antes de imprimir el mensaje Hola Mundo.

Luego, inicializamos el bucle de eventos con new_event_loop() y con set_event_loop() lo establecemos. A partir de este momento, ya podemos usar la función run() para ejecutar la corrutina main() en el bucle de eventos.

El bucle de eventos es el corazón de asyncio. Es un bucle que espera y despacha eventos y llamadas de retorno. Es un ciclo que se encarga de ejecutar las tareas que se le han asignado. Cuando una tarea se completa, el bucle de eventos la retira de la cola y ejecuta la siguiente tarea.

A partir del ejemplo de antes, podemos ir un pasito más allá y hacer algo, no mucho más útil, pero sí más entretenido. ¡Vamos a contar hasta diez!

import asyncio

async def count():
    for i in range(1, 11):
        await asyncio.sleep(1)
        print(i, end="\r")

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
asyncio.run(count())

En este caso, imprimimos los números del 1 al 10 esperando un segundo entre cada iteración.

¿Has pillado los conceptos básicos? Va, que ahora sí que haremos algo más serio. Vamos a hacer una petición a tres sitios web de forma simultánea y devolveremos el código de estado de cada petición:

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return response.status

async def main():
    async with aiohttp.ClientSession() as session:
        urls = ['https://www.google.com', 'https://www.bing.com', 'https://www.twitter.com']
        tasks = []
        for url in urls:
            task = asyncio.ensure_future(fetch(session, url))
            tasks.append(task)
        responses = await asyncio.gather(*tasks)
        print(responses)


loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
asyncio.run(main())

Hemos creado una función fetch() que realiza una solicitud HTTP asíncrona a partir de una sesión y URL que le pasemos. En la función main() utilizamos la biblioteca aiohttp para crear la sesión. Guardamos en una lista las tres URL's a las que queremos hacer la petición GET.

Lo interesante está aquí: en otra lista guardamos las tres tareas que se tienen que ejecutar simultáneamente (las peticiones GET a las URL's). Esto lo hacemos con la función ensure_future(), y luego, con la función gather() esperamos a que se completen todas las tareas.

Ya solo te queda probar a ti.

En este post hemos visto cómo podemos generar corrutinas que se ejecuten de forma asíncrona con la librería asyncio de Python. Has visto cómo crear un bucle de eventos y cómo usarlo para gestionar varias tareas al mismo tiempo.