asyncio + aiohttp: перекрытие ввода-вывода со сном - PullRequest
0 голосов
/ 13 ноября 2018

Когда все сопрограммы ждут, Asyncio прослушивает события, чтобы разбудить их снова. Типичным примером будет asyncio.sleep(), который регистрирует синхронизированное событие. На практике событие обычно представляет собой сокет ввода-вывода, готовый для приема или отправки новых данных.

Чтобы лучше понять это поведение, я настроил простой тест: он отправляет http-запрос на localhost и ждет ответа. На локальном хосте я настроил флеш-сервер, который ждет 1 секунду, прежде чем ответить. После отправки запроса клиент спит в течение 1 секунды, затем он ожидает ответа. Я ожидаю, что это вернется примерно в секунду, так как и моя программа, и сервер должны работать параллельно. Но это занимает 2 секунды:

import aiohttp
import asyncio
from time import perf_counter

async def main():
    async with aiohttp.ClientSession() as session:

        # this http request will take 1 second to respond
        async with session.get("http://127.0.0.1:5000/") as response:

            # yield control for 1 second
            await asyncio.sleep(1)

            # wait for the http request to return
            text = await response.text()
            return text

loop = asyncio.get_event_loop()

start = perf_counter()
results = loop.run_until_complete(main())
stop = perf_counter()

print(f"took {stop-start} seconds") # 2.01909

Что здесь делает asyncio, почему я не могу перекрывать время ожидания?

Меня не интересует конкретный сценарий HTTP-запросов, aiohttp используется только для построения примера. Что, вероятно, немного опасно: это может быть связано с aiohttp, а не с asyncio.

На самом деле, я ожидаю, что это так (отсюда и название вопроса об asyncio и aiohttp). Моя первая интуиция заключалась в том, что, возможно, запрос не был отправлен до вызова asyncio.sleep()., поэтому я немного переупорядочил:

# start coroutine
text = response.text()

# yield control for 1 second
await asyncio.sleep(1)

# wait for the http request to return
text = await text

Но это все равно занимает две секунды.

Хорошо, теперь, чтобы быть уверенным, что запрос был отправлен перед сном, я добавил print("incoming") к маршруту на сервере, прежде чем он перейдет в спящий режим. Я также изменил продолжительность сна на 10 секунд на клиенте. Сервер печатает входящие сразу после запуска клиента. Всего клиент занимает 11 секунд.

@app.route('/')
def index():
    print("incoming")
    time.sleep(1)
    return 'done'

Поскольку HTTP-запрос выполняется немедленно, сервер определенно отправил ответ до того, как клиент проснется с asyncio.sleep(). Мне кажется, что сокет, обеспечивающий HTTP-запрос, должен быть готов, как только клиент проснется. Но, тем не менее, общее время выполнения - это всегда время ожидания клиента и сервера.

Я как-то неправильно использую asyncio или это все-таки связано с aiohttp?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

В вашем тестовом коде есть три события ожидания (два явных и одно скрытое в async with) в серии , поэтому параллельное ожидание не будет.Код, который тестирует сценарий, который вы описываете, выглядит примерно так:

async def download():
    async with aiohttp.ClientSession() as session:
        async with session.get("http://127.0.0.1:5000/") as response:
            text = await response.text()
            return text

async def main():
    loop = asyncio.get_event_loop()
    # have download start "in the background"
    dltask = loop.create_task(download())
    # now sleep
    await asyncio.sleep(1)
    # and now await the end of the download
    text = await dltask

Запуск Эта сопрограмма должна занять ожидаемое время.

0 голосов
/ 13 ноября 2018

Проблема в том, что одна секунда происходит на сервере и выполняется в async with session.get("http://127.0.0.1:5000/") as response:.

HTTP-запрос завершается до того, как вы получите этот response объект.

Вы можете проверить это:

...
async def main():
    async with aiohttp.ClientSession() as session:

        start = perf_counter()
        # this http request will take 1 second to respond
        async with session.get("http://127.0.0.1:5000/") as response:
            end = perf_counter()
            print(f"took {end-start} seconds to get response")
            # yield control for 1 second
            await asyncio.sleep(1)

            # wait for the http request to return
            text = await response.text()
            return text
...

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

...