Мне нужно отправить много HTTP-запросов, после того как все они вернутся, программа может продолжаться. Похоже, идеально подходит для asyncio
. Немного наивно я обернул свои вызовы в requests
функцией async
и передал их asyncio
. Это не работает
После поиска в Интернете я нашел два решения:
- используйте библиотеку типа aiohttp , которая предназначена для работы с
asyncio
- обернуть код блокировки при вызове на
run_in_executor
Чтобы лучше это понять, я написал небольшой тест. На стороне сервера находится программа-колба, которая ждет 0,1 секунды, прежде чем ответить на запрос.
from flask import Flask
import time
app = Flask(__name__)
@app.route('/')
def hello_world():
time.sleep(0.1) // heavy calculations here :)
return 'Hello World!'
if __name__ == '__main__':
app.run()
Клиент - мой эталон
import requests
from time import perf_counter, sleep
# this is the baseline, sequential calls to requests.get
start = perf_counter()
for i in range(10):
r = requests.get("http://127.0.0.1:5000/")
stop = perf_counter()
print(f"synchronous took {stop-start} seconds") # 1.062 secs
# now the naive asyncio version
import asyncio
loop = asyncio.get_event_loop()
async def get_response():
r = requests.get("http://127.0.0.1:5000/")
start = perf_counter()
loop.run_until_complete(asyncio.gather(*[get_response() for i in range(10)]))
stop = perf_counter()
print(f"asynchronous took {stop-start} seconds") # 1.049 secs
# the fast asyncio version
start = perf_counter()
loop.run_until_complete(asyncio.gather(
*[loop.run_in_executor(None, requests.get, 'http://127.0.0.1:5000/') for i in range(10)]))
stop = perf_counter()
print(f"asynchronous (executor) took {stop-start} seconds") # 0.122 secs
#finally, aiohttp
import aiohttp
async def get_response(session):
async with session.get("http://127.0.0.1:5000/") as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
await get_response(session)
start = perf_counter()
loop.run_until_complete(asyncio.gather(*[main() for i in range(10)]))
stop = perf_counter()
print(f"aiohttp took {stop-start} seconds") # 0.121 secs
Итак, интуитивно понятная реализация с asyncio
не имеет дело с блокировкой io-кода. Но если вы используете asyncio
правильно, это так же быстро, как специальный aiohttp
фреймворк. Документы для сопрограмм и задач на самом деле не упоминают об этом. Только если вы читаете в loop.run_in_executor () , он говорит:
# File operations (such as logging) can block the
# event loop: run them in a thread pool.
Я был удивлен таким поведением. Цель asyncio - ускорить блокировку вызовов. Почему для этого необходима дополнительная обёртка run_in_executor
?
Весь смысл продажи aiohttp
кажется поддержкой для asyncio
. Но, насколько я вижу, модуль requests
работает отлично - до тех пор, пока вы заключаете его в исполнителя. Есть ли причина избегать чего-либо в исполнителе?