Понимание asyncio в python

Мне было трудно понять модуль asyncio Python и как не блокировать асинхронные вызовы. Например, учитывая этот фрагмент кода:

import aiohttp
import asyncio
import async_timeout

async def fetch(session, url):
    with async_timeout.timeout(10):
        async with session.get(url) as response:
            return await response.text()

async def main(loop):
    print(1)
    async with aiohttp.ClientSession(loop=loop) as session:
        html = await fetch(session, 'http://python.org')
        print(html)
    print(2)

loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))

Я ожидаю, что аналогично тому, как это будет работать в Javascript, вывод будет

1
2
<!doctype html>
<...>

Вместо этого функция печатает 1, блокирует, пока не вернет html, затем печатает 2. Почему она блокируется и как/можно избежать блокировки? Спасибо за вашу помощь.


person rumdrums    schedule 23.02.2017    source источник


Ответы (2)


Ваша проблема в этой строке:

html = await fetch(session, 'http://python.org')

это означает - дождаться получения ответа от выборки, или, другими словами, блокировать текущую сопрограмму, пока сопрограмма выборки не вернет текст ответа.

Чтобы упростить задачу:

  • Операция выборки сама по себе не блокируется, только ожидание.
  • Ожидание блокирует только текущую сопрограмму, а не поток.
  • Как сказал винсент, вызов операции сам по себе ничего не делает (мы не запускаем процесс выборки)

из документации по asyncio

Вызов сопрограммы не запускает ее код — объект сопрограммы, возвращаемый вызовом, ничего не делает, пока вы не запланируете его выполнение. Есть два основных способа запустить его: вызвать ожидающую сопрограмму или выйти из сопрограммы из другой сопрограммы (при условии, что другая сопрограмма уже запущена!), или запланировать ее выполнение с помощью функции sure_future() или метода AbstractEventLoop.create_task().

И исправление (для получения желаемого результата):

import asyncio
import aiohttp
import async_timeout


async def fetch(session, url):
    with async_timeout.timeout(10):
        async with session.get(url) as response:
            return await response.text()


async def main(loop):
    print(1)
    async with aiohttp.ClientSession(loop=loop) as session:
        future = asyncio.ensure_future(fetch(session, 'http://python.org')) # we start the fetch process
        print(2)
        html = await future # we wait for getting the fetch response
        print(html)


loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
person etlsh    schedule 23.02.2017
comment
Вызов сопрограммы fetch не планирует операцию выборки и не возвращает будущее. Вы должны использовать asyncio.ensure_future чтобы этого добиться. - person Vincent; 23.02.2017
comment
Спасибо за исправление моего ответа, я отредактировал свой код и добавил дополнительное объяснение. - person etlsh; 23.02.2017
comment
Спасибо, это объяснение было очень полезным. Итак, если бы я действительно хотел запустить сопрограмму, не блокируя выполнение остальной части функции... Мне фактически нужно было бы ожидать asyncio.sleep(0) после asyncio.ensure_future?? Есть ли лучший способ сделать это? - person rumdrums; 23.02.2017
comment
используя asyncio.ensure_future, запустите сопрограмму и не блокируйте остальную часть функции. - person etlsh; 23.02.2017
comment
@etlsh Думаю, я имею в виду, что если я добавлю await asyncio.sleep(0) сразу после asyncio.ensure_future, он фактически начнет выполнение сопрограммы до 2 отпечатков. В противном случае он даже не начнется до тех пор, пока не будут сделаны два отпечатка, верно? - person rumdrums; 24.02.2017
comment
да, вам нужно выполнить какую-то асинхронную операцию (и дать циклу событий время для планирования) - person etlsh; 24.02.2017

Чтобы понять asyncio в стиле python 3.6 async def, я предлагаю вам запустить этот простой пример:

import asyncio
import time

def write(msg):
    print(msg, flush=True)

async def say1():
    await asyncio.sleep(1)
    write("Hello 1!")

async def say2():
    await asyncio.sleep(1)
    write("Hello 2!")

write("start")
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
    say1(),
    say2()
))
write("exit")

loop.close()

Источник: https://maketips.net/tip/146/parallel-execution-of-asyncio-functions См. ссылку для подробного объяснения этого кода с диаграммой вызовов.

person dekassert    schedule 23.02.2017