Saltar al contenido

El ejemplo más simple de async / await posible en Python

Solución:

Para responder a sus preguntas, proporcionaré 3 soluciones diferentes al mismo problema.

caso 1: solo pitón normal

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

caso 2: async / await hecho mal

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

caso 3: async / await hecho bien (igual que en el caso 2, excepto que sleep función)

import asyncio
import time

async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    await asyncio.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 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 sec

case 1 con case 2 dar lo mismo 5 seconds, mientras que case 3 solo 3 seconds. Entonces el async/await done right es más rápido.

La razón de la diferencia está en la implementación de sleep función.

# case 1
def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

# case 2
async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

# case 3
async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    await asyncio.sleep(1)

sleep funcionar en case 1 y case 2 son lo mismo”. Ellos “duermen” sin permitir que otros usen los recursos. Mientras que case 3 permite el acceso a los recursos cuando está dormido.

En case 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 le permite al bucle interrumpir su función para ejecutar otra tarea.

En case 3 le dijimos al bucle de eventos exactamente dónde interrumpir la función para ejecutar otra tarea. ¿Donde exactamente?

# case 3
async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    await asyncio.sleep(1) # <-- Right here!

Más sobre esto lea aquí

Actualización 02 / mayo / 2020

Considere leer

  • Una guía para autostopistas sobre programación asincrónica
  • Asyncio Futures y Coroutines

¿Es posible dar un ejemplo sencillo que muestre cómo async / await
funciona, usando solo estas dos palabras clave + asyncio.get_event_loop() +
run_until_complete + otro código Python pero ningún otro asyncio funciones?

De esta forma 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é necesitas asyncio.

Por cierto, ¿por qué necesitas? asyncio, no solo código simple? La respuesta es – asyncio le permite obtener beneficios de rendimiento cuando paraleliza las operaciones de bloqueo de E / S (como leer / escribir 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.

Upd:

ok, aquí tienes 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 múltiples 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]

Note como ambos io_related Comenzó entonces, 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 “asegurar_futuro”): puede usar create_task que devuelve un objeto Task (que puede ser útil más adelante para cancelar la tarea si es necesario).

Ejemplo básico 1

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 loop

Resultado:

hola 1 comencé
hola 2 comenzó
hola 1 hecho
hola 2 hecho


Ejemplo básico 2

Si necesita obtener el valor de retorno de estas funciones asincrónicas, entonces gather es útil. El siguiente ejemplo está inspirado en la documentación, pero desafortunadamente el documento no muestra lo que gather es realmente útil para: 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 me ayudó a asimilar la programación asincrónica de Python.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *