Módulo Python asyncio

asyncioEs un módulo de la biblioteca estándar de Python, utilizado para escribir código de operaciones de E/S asíncronas.

asyncio proporciona una manera eficiente de manejar tareas concurrentes, especialmente adecuada para operaciones intensivas en E/S, como solicitudes de red, lectura/escritura de archivos, etc.

Al usar `asyncio`asyncio, puedes manejar múltiples tareas simultáneamente en un solo hilo, sin necesidad de usar múltiples hilos o múltiples procesos.

¿Por qué se necesita asyncio?

En la programación síncrona tradicional, cuando una tarea necesita esperar a que se complete una operación de E/S (como una solicitud de red), el programa se bloquea hasta que la operación finaliza. Esto provoca una baja eficiencia del programa, especialmente cuando se necesitan manejar muchas operaciones de E/S.

asyncioAl introducir el modelo de programación asíncrona, el programa puede continuar ejecutando otras tareas mientras espera operaciones de E/S, mejorando así la concurrencia y la eficiencia del programa.

Imagina que estás administrando un restaurante:

  • Modo síncrono (funciones normales):Tienes un solo chef. El cliente A pide un filete, el chef comienza a cocinarlo (esto requiere esperar 5 minutos). Durante esos 5 minutos de cocción, el chef está completamente ocupado y no puede hacer nada más; incluso si el cliente B solo quiere pedir un vaso de agua, tiene que esperar sin hacer nada.
  • Modo asíncrono (asyncio):Tienes varios chefs (en realidad sigue siendo uno, pero muy inteligente). Después de que el chef comienza a cocinar el filete del cliente A, se da cuenta de que necesita esperar; inmediatamente marca el filete como en espera y luego se dirige a servir agua al cliente B. Al volver, revisa si el filete está casi listo; si aún no lo está, puede atender el pedido del cliente C. Así, durante el tiempo de espera de E/S (como cocinar el filete, solicitudes de red, lectura/escritura de archivos), el chef (CPU) trabaja eficientemente.

asyncio es la biblioteca estándar que Python utiliza para implementar este inteligente modo de trabajo. Te permite escribir código de concurrencia de un solo hilo, especialmente adecuado para escenarios intensivos en E/S como rastreadores web, servidores web, microservicios, etc.

Su núcleo son el bucle de eventos, las corrutinas y las tareas.


Conceptos centrales de asyncio

1. Corrutina (Coroutine)

Una corrutina esasynciouno de los conceptos centrales de `asyncio`. Es una función especial que puede pausarse durante su ejecución y reanudarse más tarde. Las corrutinas se definen medianteasync defla palabra clave `async def` y, medianteawaitla palabra clave `await` se pausa la ejecución, esperando que se complete la operación asíncrona.

Ejemplo

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

2. Bucle de eventos (Event Loop)

El bucle de eventos esasyncioel componente central de `asyncio`, responsable de programar y ejecutar corrutinas. Comprueba continuamente si hay tareas que ejecutar y, cuando se completan, llama a las funciones de devolución de llamada correspondientes.

Ejemplo

async def main():
    await say_hello()

asyncio.run(main())

3. Tarea (Task)

Una tarea es una envoltura de una corrutina, que representa una corrutina en ejecución o por ejecutarse. Puedesasyncio.create_task()usar la función `create_task()` para crear tareas y agregarlas al bucle de eventos.

Ejemplo

async def main():
    task = asyncio.create_task(say_hello())
    await task

4. Future

Future`Future` es un objeto que representa el resultado de una operación asíncrona. Generalmente se usa en API de bajo nivel para representar una operación aún no completada. Puedesawaitusar la palabra clave `await` para esperarFuturea que `Future` se complete.

Ejemplo

async def main():
    future = asyncio.Future()
    await future

Uso básico y ejemplos de código

Supongamos que necesitamos obtener el contenido de tres URLs diferentes. Con el modo síncrono se ejecutarán secuencialmente, y el tiempo total será la suma de los tiempos de las tres solicitudes. Usando

`asyncio`, podemos enviar las tres solicitudes simultáneamente, y el tiempo total se acercará al de la solicitud más lenta.asyncioVersión síncrona (como comparación)

Versión síncrona (como comparación)

Ejemplo

import time
import requests

def fetch_url(url):
    `"Iniciando obtención: {url}"`
    print(f`# Simula 2 segundos de latencia de red`)
    time.sleep(2)  `"Obtención completada: {url}"`
    print(f`"Datos de {url}"`)
    return f`# Debe esperar a que el anterior termine para comenzar el siguiente`

def main_sync():
    urls = ['https://example.com/1', 'https://example.com/2', 'https://example.com/3']
    results = []
    start = time.time()
   
    for url in urls:
        result = fetch_url(url)  `"Tiempo total de la versión síncrona: {end - start:.2f} segundos"`
        results.append(result)
   
    end = time.time()
    print(f`"Resultados: {results}"`)
    print(fResultado esperado de ejecución)

if __name__ == "__main__":
    main_sync()

En total tomó aproximadamente 6 segundos.:

开始获取: https://example.com/1
完成获取: https://example.com/1
开始获取: https://example.com/2
完成获取: https://example.com/2
开始获取: https://example.com/3
完成获取: https://example.com/3
同步版本总耗时: 6.00 秒
结果: [‘来自 https://example.com/1 的数据‘, ‘来自 https://example.com/2 的数据‘, ‘来自 https://example.com/2 的数据‘]

Versión asíncrona (usando asyncio)

Versión asíncrona (usando asyncio)

la biblioteca `aiohttp` en lugar deaiohttp`requests` para realizar solicitudes HTTP asíncronas. Primero instálala:requestsEjemplopip install aiohttp。

Ejemplo

import asyncio
import aiohttp
import time

async def fetch_url_async(session, url):
    `"Iniciando obtención asíncrona: {url}"`
    print(f`# Nota: aquí usamos el método asíncrono get de aiohttp, y esperamos con await`)
    `# Simula que procesar la respuesta también requiere tiempo`
    async with session.get(url) as response:
        `# Usa asyncio.sleep para simular la espera de E/S; no bloquea el hilo`
        await asyncio.sleep(2)  `"Obtención asíncrona completada: {url}"`
        text = await response.text()
        print(f`"Datos de {url} (longitud: {len(text)})"`)
        return f`# Crea una sesión HTTP asíncrona`

async def main_async():
    urls = ['https://httpbin.org/get', 'https://httpbin.org/delay/1', 'https://httpbin.org/headers']
   
    async with aiohttp.ClientSession() as session:  `# Crea una tarea (Task) para cada URL`
        `# create_task agregará la corrutina al bucle de eventos y comenzará a programarla de inmediato`
        tasks = []
        for url in urls:
            `"Todas las tareas han sido creadas, comenzando la ejecución concurrente..."`
            task = asyncio.create_task(fetch_url_async(session, url))
            tasks.append(task)
       
        print(`# Usa asyncio.gather para ejecutar todas las tareas concurrentemente y esperar a que todas terminen`)
       
        `# gather devuelve una lista de resultados, cuyo orden coincide con el orden de las tareas pasadas`
        `# asyncio.run() es una forma conveniente de iniciar el bucle de eventos y ejecutar la corrutina principal`
        results = await asyncio.gather(*tasks)
       
        return results

if __name__ == "__main__":
    start = time.time()
    `"Tiempo total de la versión asíncrona: {end - start:.2f} segundos"`
    final_results = asyncio.run(main_async())
    end = time.time()
   
    print(f"\nResultado esperado de ejecución)
    for res in final_results:
        print(res)

Análisis del código:

所有任务已创建,开始并发执行...
开始异步获取: https://httpbin.org/get
开始异步获取: https://httpbin.org/delay/1
开始异步获取: https://httpbin.org/headers
(大约 2 秒后,所有请求几乎同时完成)
完成异步获取: https://httpbin.org/headers
完成异步获取: https://httpbin.org/get
完成异步获取: https://httpbin.org/delay/1

异步版本总耗时: 2.10 秒  # 注意!总耗时远小于 6 秒
来自 https://httpbin.org/get 的数据 (长度: 274)
来自 https://httpbin.org/delay/1 的数据 (长度: 392)
来自 https://httpbin.org/headers 的数据 (长度: 177)

`async def fetch_url()`: define la función de corrutina:

  • async def`await asyncio.sleep(2)`: enfetch_url_asyncymain_async。
  • await`fetch_url`, usamos `await asyncio.sleep(2)`fetch_url_async, lo que le dice al bucle de eventos: "Esta solicitud de red necesita tiempo, ve a ejecutar otras tareas listas".await session.get()yawait response.text()`asyncio.create_task()`: envuelve
  • asyncio.create_task()la corrutina `fetch_url` en unfetch_url_async`Task`, lo que permite que el bucle de eventos la programe, logrando concurrencia.Task`asyncio.gather()`: una función muy práctica que ejecuta concurrentemente todas las corrutinas/tareas pasadas, espera a que todas terminen y, finalmente, recopila todos los resultados.
  • asyncio.gather(*tasks)`asyncio.run()`: el método recomendado en Python 3.7+, que se encarga de crear el bucle de eventos, ejecutar la corrutina y cerrar el bucle.
  • asyncio.run(main_async())Explicación de funciones clave y parámetros

Explicación de funciones clave y parámetros

algunas de las funciones avanzadas más utilizadas en `asyncio`:asyncioFunción

Propósito principal Descripción de parámetros comunes Ejecuta una corrutina de nivel superior y gestiona el ciclo de vida del bucle de eventos. Es el punto de entrada principal del programa.
asyncio.run(coro, *, debug=False) `coro`: el objeto de corrutina que se va a ejecutar. coro`debug`: si se establece en
debug`True`, se puede habilitar el modo de depuración del bucle de eventos.TrueEnvuelve una corrutina en un
asyncio.create_task(coro, *, name=None) objeto `Task` y lo coloca en el bucle de eventos para que sea programado. Esta es la principal forma de lograr concurrencia.Task`coro`: el objeto de corrutina que se va a envolver. coro`name`: (Python 3.8+) especifica un nombre para la tarea, lo que facilita la depuración.
nameEjecuta concurrentemente
asyncio.gather(*aws, return_exceptions=False) múltiples tareas asíncronas (acepta corrutinas, tareas, etc.), espera a que todas se completen y devuelve una lista de resultados.aws`*aws`: parámetro variable, se pasan múltiples objetos asíncronos. *aws`return_exceptions`: por defecto es
return_exceptions, cualquier excepción lanzada por una tarea se propagará inmediatamente aFalsequien llama a `gather`. Si se establece engather`True`, las excepciones se devolverán como parte de los resultados.TrueDe forma asíncrona
asyncio.sleep(delay, result=None) duerme la cantidad de segundos especificada. Esta es la diferencia clave con`time.sleep()` (bloqueante).time.sleepLa diferencia clave de (bloqueo). delayNúmero de segundos para dormir.
resultValor que se devuelve después de que termina el sueño.
asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED) Ejecutar tareas concurrentemente y esperar a que se cumpla la condición especificada. Devuelve dos conjuntos(done, pending), que son las tareas completadas y las no completadas, respectivamente. awsConjunto de objetos asíncronos.
timeoutTiempo de espera (en segundos).
return_whenDetermina cuándo regresar, opcional:FIRST_COMPLETED(el primero en completarse),FIRST_EXCEPTION(la primera excepción),ALL_COMPLETED(todos completados, por defecto).
asyncio.to_thread(func, /, *args, **kwargs) (Python 3.9+) Toma una función síncrona ordinaria y potencialmente bloqueante, la ejecuta en un hilo separado y devuelve unaawaitcoroutine que se puede esperar. Se utiliza para manejar trabajo intensivo de CPU o E/S bloqueante. funcLa función síncrona que se ejecutará en el hilo.
*args, **kwargsLos argumentos que se pasan a la función.

Comprensión visual: flujo de programación de tareas asíncronas

Explicación gráfica:awaitPor ejemplo, cuando espera una respuesta de la red, queda suspendida y el bucle de eventos inmediatamente busca en la cola la siguiente tarea que pueda ejecutarse (lista) para ejecutarla. Cuando la operación de E/S de la tarea suspendida se completa, el bucle de eventos recibe la notificación, cambia el estado de la tarea de nuevo a lista y continúa ejecutándola en algún momento futuro. De esta manera, durante la espera de E/S, la CPU se utiliza plenamente para ejecutar otras tareas, logrando concurrencia dentro de un solo hilo.


Uso básico de asyncio

1. Ejecutar una corrutina

Para ejecutar una corutina, puedes usar laasyncio.run()función. Esta crea un bucle de eventos y ejecuta la corutina especificada.

Ejemplo

import asyncio

async def main():
    print("Start")
    await asyncio.sleep(1)
    print("End")

asyncio.run(main())

2. Ejecutar múltiples tareas concurrentemente

Puedes usar laasyncio.gather()función para ejecutar varias corutinas concurrentemente y esperar a que todas se completen.

Ejemplo

import asyncio

async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(2)
    print("Task 2 finished")

async def main():
    await asyncio.gather(task1(), task2())

asyncio.run(main())

3. Control de tiempo de espera

Puedes usar laasyncio.wait_for()función para establecer un tiempo de espera para la corutina. Si la corutina no se completa en el tiempo especificado, se lanzaráasyncio.TimeoutErroruna excepción.

Ejemplo

import asyncio

async def long_task():
    await asyncio.sleep(10)
    print("Task finished")

async def main():
    try:
        await asyncio.wait_for(long_task(), timeout=5)
    except asyncio.TimeoutError:
        print("Task timed out")

asyncio.run(main())

Escenarios de aplicación de asyncio

asyncioEs especialmente adecuado para los siguientes escenarios:

  1. Solicitudes de red: como solicitudes HTTP, comunicación WebSocket, etc.
  2. E/S de archivos: como lectura y escritura de archivos asíncronas.
  3. Operaciones con bases de datos: como acceso asíncrono a bases de datos.
  4. Procesamiento de datos en tiempo real: como procesamiento de colas de mensajes en tiempo real.

Clases, métodos y funciones comunes

1. Funciones principales

Método/Función Descripción Ejemplo
asyncio.run(coro) Ejecutar la función principal asíncrona (Python 3.7+) asyncio.run(main())
asyncio.create_task(coro) Crear tarea y añadirla al bucle de eventos task = asyncio.create_task(fetch_data())
asyncio.gather(*coros) Ejecutar varias corutinas concurrentemente await asyncio.gather(task1, task2)
asyncio.sleep(delay) Espera asíncrona (no bloqueante) await asyncio.sleep(1)
asyncio.wait(coros) Controlar el modo de finalización de las tareas done, pending = await asyncio.wait([task1, task2])

2. Bucle de eventos (Event Loop)

Método Descripción Ejemplo
loop.run_until_complete(future) Ejecutar hasta que la tarea se complete loop.run_until_complete(main())
loop.run_forever() Ejecutar el bucle de eventos para siempre loop.run_forever()
loop.stop() Detener el bucle de eventos loop.stop()
loop.close() Cerrar el bucle de eventos loop.close()
loop.call_soon(callback) Programar la ejecución inmediata de una función de devolución de llamada loop.call_soon(print, "Hello")
loop.call_later(delay, callback) Ejecución retrasada de la función de devolución de llamada loop.call_later(5, callback)

3. Corutinas (Coroutine) y tareas (Task)

Método/Decorador Descripción Ejemplo
@asyncio.coroutine Decorador de corutinas (versión anterior, Python 3.4-3.7) @asyncio.coroutine
def old_coro():
async def Definir corutina (Python 3.5+) async def fetch():
task.cancel() Cancelar tarea task.cancel()
task.done() Verificar si la tarea está completada if task.done():
task.result() Obtener el resultado de la tarea (requiere que la tarea esté completada) data = task.result()

4. Primitivas de sincronización (similares athreading)

Clase Descripción Ejemplo
asyncio.Lock() Candado de exclusión mutua asíncrono lock = asyncio.Lock()
async with lock:
asyncio.Event() Notificación de eventos event = asyncio.Event()
await event.wait()
asyncio.Queue() Cola asíncrona queue = asyncio.Queue()
await queue.put(item)
asyncio.Semaphore() Semáforo sem = asyncio.Semaphore(5)
async with sem:

5. Redes y subprocesos

Método/Clase Descripción Ejemplo
asyncio.open_connection() Establecer conexión TCP reader, writer = await asyncio.open_connection('host', 80)
asyncio.start_server() Crear servidor TCP server = await asyncio.start_server(handle, '0.0.0.0', 8888)
asyncio.create_subprocess_exec() Crear subproceso proc = await asyncio.create_subprocess_exec('ls')

6. Herramientas útiles

Método Descripción Ejemplo
asyncio.current_task() Obtener la tarea actual task = asyncio.current_task()
asyncio.all_tasks() Obtener todas las tareas tasks = asyncio.all_tasks()
asyncio.shield(coro) Proteger la tarea de la cancelación await asyncio.shield(critical_task)
asyncio.wait_for(coro, timeout) Espera con tiempo de espera try: await asyncio.wait_for(task, 5)

Ejemplo

1. Ejemplo básico de corutina

Ejemplo

import asyncio

async def hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

asyncio.run(hello())  # Python 3.7+

2. Ejecución concurrente de tareas

Ejemplo

async def fetch(url):
    print(f"Fetching {url}")
    await asyncio.sleep(2)
    return f"Data from {url}"

async def main():
    results = await asyncio.gather(
        fetch("url1.com"),
        fetch("url2.com")
    )
    print(results)

asyncio.run(main())

3. Usar una cola asíncrona

Ejemplo

async def producer(queue):
    for i in range(5):
        await queue.put(i)
        await asyncio.sleep(0.1)

async def consumer(queue):
    while True:
        item = await queue.get()
        print(f"Consumed {item}")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(
        producer(queue),
        consumer(queue)
    )

Notas importantes

  1. Versión de Python: algunas funciones requieren Python 3.7+ (por ejemplo,asyncio.run())。

  2. Operaciones bloqueantes: evita usar código síncrono bloqueante en las corutinas (por ejemplo,time.sleep())。

  3. Depuración: configurar laPYTHONASYNCIODEBUG=1variable de entorno habilita el modo de depuración.

  4. Cancelación de tareas: una tarea cancelada lanzaCancelledError, por lo que debe manejarse adecuadamente.

Otras extensiones