Самый простой пример async/wait, возможно, в Python

Я читал много примеров, сообщений в блогах, вопросов/ответов об asyncio/async/await в Python 3. 5+, многие из них были сложными, простейшее, что я нашел, вероятно, было таким. Тем не менее он использует ensure_future и для обучения асинхронному программированию в Python, я хотел бы увидеть, возможен ли еще более минимальный пример (т.е. Какие минимальные инструменты необходимы для выполнения базового примера async/await).

Вопрос: для целей обучения асинхронному программированию в Python можно привести простой пример, показывающий, как работает async/await, используя только эти два ключевых слова + asyncio.get_event_loop() + run_until_complete + другой код Python, но никаких других функций asyncio?

Пример: что-то вроде этого:

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())

но без ensure_future и все еще демонстрирует, как работает await/async.

Ответ 1

можно ли дать простой пример, показывающий, как работает async/await, используя только эти два ключевых слова + asyncio.get_event_loop() + run_until_complete + другой код Python, но никаких других функций asyncio?

Таким образом, можно написать код, который работает:

import asyncio


async def main():
    print('done!')


if __name__ ==  '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Но таким образом невозможно продемонстрировать, почему вам нужен асинчо.

Кстати, зачем вам asyncio, а не просто код? Ответ. - asyncio позволяет вам получать выгоду от производительности при параллелизации операций блокировки ввода-вывода (например, чтение/запись в сеть). И чтобы написать полезный пример, вам нужно использовать асинхронную реализацию этих операций.

Пожалуйста, прочитайте этот ответ для более подробного объяснения.

Upd:

ok, здесь пример, который использует asyncio.sleep для имитации операции блокировки ввода-вывода и asyncio.gather который показывает, как вы можете одновременно выполнять несколько операций блокировки:

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())

Вывод:

first started
second started
first finished
second finished
[Finished in 1.2s]

Обратите внимание на то, как оба io_related начали, а затем через одну секунду, оба сделаны.

Ответ 2

Чтобы ответить на ваши вопросы, я предоставлю 3 разных решения одной и той же проблемы.

случай 1: просто нормальный питон

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')

выход:

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

дело 2: асинхронное/ожидание выполнено неправильно

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')

выход:

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

case 3: async/await выполнено правильно (так же, как в случае 2, за исключением функции sleep)

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')

выход:

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 с case 2 дают то же самое 5 seconds, тогда как case 3 просто 3 seconds. Так что async/await done right быстрее.

Причина различий заключается в реализации функции sleep.

# 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 в case 1 и case 2 "одинаковы". Они "спят", не позволяя другим использовать ресурсы. Принимая во внимание, что case 3 разрешает доступ к ресурсам, когда он спит.

В case 2 мы добавили async к нормальной функции. Однако цикл обработки событий запустит его без перерыва. Почему? Потому что мы не сказали, где циклу разрешено прерывать вашу функцию для запуска другой задачи.

В case 3 мы указали в цикле событий, где именно нужно прервать функцию для запуска другой задачи. Где именно?

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

Подробнее об этом читайте здесь here