He leído muchos ejemplos, publicaciones de blog, preguntas/respuestas sobre asyncio / async / await en Python 3.5+, muchos eran complejos, el más simple que encontré probablemente fue este .
Aún así, usa ensure_future y, para fines de aprendizaje sobre la programación asíncrona en Python, me gustaría ver un ejemplo aún más mínimo y cuáles son las herramientas mínimas necesarias para hacer un ejemplo básico de async/await.
Pregunta: ¿es posible dar un ejemplo simple que muestre cómo funciona async / await , usando solo estas dos palabras clave + código para ejecutar el ciclo asíncrono + otro código de Python pero ninguna otra función asyncio ?
Ejemplo: algo como esto:
import asyncio async def async_foo(): print("async_foo started") await asyncio.sleep(5) print("async_foo done") async def main(): asyncio.ensure_future(async_foo()) # fire and forget async_foo() print('Do some actions 1') await asyncio.sleep(5) print('Do some actions 2') loop = asyncio.get_event_loop() loop.run_until_complete(main()) pero sin asegurar_futuro, y aún demuestra cómo funciona ensure_future /async.
Para responder a sus preguntas, proporcionaré 3 soluciones diferentes para el mismo problema.
import time def sleep(): print(f'Time: {time.time() - start:.2f}') time.sleep(1) def sum(name, numbers): total = 0 for number in numbers: print(f'Task {name}: Computing {total}+{number}') sleep() total += number print(f'Task {name}: Sum = {total}\n') start = time.time() tasks = [ sum("A", [1, 2]), sum("B", [1, 2, 3]), ] end = time.time() print(f'Time: {end-start:.2f} sec')producción:
Task A: Computing 0+1 Time: 0.00 Task A: Computing 1+2 Time: 1.00 Task A: Sum = 3 Task B: Computing 0+1 Time: 2.01 Task B: Computing 1+2 Time: 3.01 Task B: Computing 3+3 Time: 4.01 Task B: Sum = 6 Time: 5.02 sec import asyncio import time async def sleep(): print(f'Time: {time.time() - start:.2f}') time.sleep(1) async def sum(name, numbers): total = 0 for number in numbers: print(f'Task {name}: Computing {total}+{number}') await sleep() total += number print(f'Task {name}: Sum = {total}\n') start = time.time() loop = asyncio.get_event_loop() tasks = [ loop.create_task(sum("A", [1, 2])), loop.create_task(sum("B", [1, 2, 3])), ] loop.run_until_complete(asyncio.wait(tasks)) loop.close() end = time.time() print(f'Time: {end-start:.2f} sec')producción:
Task A: Computing 0+1 Time: 0.00 Task A: Computing 1+2 Time: 1.00 Task A: Sum = 3 Task B: Computing 0+1 Time: 2.01 Task B: Computing 1+2 Time: 3.01 Task B: Computing 3+3 Time: 4.01 Task B: Sum = 6 Time: 5.01 sec Igual que el caso 2 excepto la función sleep :
async def sleep(): print(f'Time: {time.time() - start:.2f}') await asyncio.sleep(1)producción:
Task A: Computing 0+1 Time: 0.00 Task B: Computing 0+1 Time: 0.00 Task A: Computing 1+2 Time: 1.00 Task B: Computing 1+2 Time: 1.00 Task A: Sum = 3 Task B: Computing 3+3 Time: 2.00 Task B: Sum = 6 Time: 3.01 secEl caso 1 y el caso 2 dan los mismos 5 segundos , mientras que el caso 3 solo 3 segundos . Entonces, el async/await bien hecho es más rápido.
La razón de la diferencia está dentro de la implementación de la función de sleep .
# case 1 def sleep(): ... time.sleep(1) # case 2 async def sleep(): ... time.sleep(1) # case 3 async def sleep(): ... await asyncio.sleep(1)En el caso 1 y el caso 2, son "lo mismo": "duermen" sin permitir que otros utilicen los recursos. Mientras que en el caso 3, permite el acceso a los recursos cuando está dormido.
En el caso 2, agregamos async a la función normal. Sin embargo, el bucle de eventos lo ejecutará sin interrupción . ¿Por qué? Porque no dijimos dónde se permite que el ciclo interrumpa su función para ejecutar otra tarea.
En el caso 3, le dijimos al ciclo de eventos exactamente dónde interrumpir la función para ejecutar otra tarea. ¿Donde exactamente? ¡Aquí mismo!
await asyncio.sleep(1)Más sobre esto leer aquí
Actualización 02/Mayo/2020
Considera leer
¿Es posible dar un ejemplo simple que muestre cómo funciona
async/await, usando solo estas dos palabras clave +asyncio.get_event_loop()+run_until_complete+ otro código de Python pero ninguna otra funciónasyncio?
De esta manera es posible escribir código que funcione:
import asyncio async def main(): print('done!') if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main())Pero de esta manera es imposible demostrar por qué necesita asyncio.
Por cierto, ¿por qué necesita asyncio , no solo un código simple? La respuesta es: asyncio le permite obtener un beneficio de rendimiento cuando paraleliza las operaciones de bloqueo de E/S (como lectura/escritura en la red). Y para escribir un ejemplo útil, debe usar la implementación asíncrona de esas operaciones.
Lea esta respuesta para obtener una explicación más detallada.
Actualización:
bien, aquí hay un ejemplo que usa asyncio.sleep para imitar la operación de bloqueo de E/S y asyncio.gather que muestra cómo puede ejecutar varias operaciones de bloqueo al mismo tiempo:
import asyncio async def io_related(name): print(f'{name} started') await asyncio.sleep(1) print(f'{name} finished') async def main(): await asyncio.gather( io_related('first'), io_related('second'), ) # 1s + 1s = over 1s if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main())Producción:
first started second started first finished second finished [Finished in 1.2s] Observe cómo comenzaron ambos io_related luego, después de solo un segundo, ambos terminaron.
Python 3.7+ ahora tiene una API más simple (en mi opinión) con una redacción más simple (más fácil de recordar que "ensure_future"): puede usar create_task que devuelve un objeto Task (que puede ser útil más adelante para cancelar la tarea si es necesario).
import asyncio async def hello(i): print(f"hello {i} started") await asyncio.sleep(4) print(f"hello {i} done") async def main(): task1 = asyncio.create_task(hello(1)) # returns immediately, the task is created await asyncio.sleep(3) task2 = asyncio.create_task(hello(2)) await task1 await task2 asyncio.run(main()) # main loopResultado:
hola 1 comenzo
hola 2 comenzo
hola 1 hecho
hola 2 hecho
Si necesita obtener el valor de retorno de estas funciones asíncronas, la gather es útil. El siguiente ejemplo está inspirado en la documentación , pero desafortunadamente el documento no muestra para qué es realmente útil gather : ¡obtener los valores de retorno!
import asyncio async def factorial(n): f = 1 for i in range(2, n + 1): print(f"Computing factorial({n}), currently i={i}...") await asyncio.sleep(1) f *= i return f async def main(): L = await asyncio.gather(factorial(2), factorial(3), factorial(4)) print(L) # [2, 6, 24] asyncio.run(main())Rendimiento esperado:
Calculando factorial(2), actualmente i=2...
Calculando factorial(3), actualmente i=2...
Calculando factorial(4), actualmente i=2...
Calculando factorial(3), actualmente i=3...
Calculando factorial(4), actualmente i=3...
Calculando factorial(4), actualmente i=4...
[2, 6, 24]
PD: incluso si usa asyncio , y no trio , el tutorial de este último fue útil para mí para asimilar la programación asincrónica de Python.