Асинхронность в FastAPI / Python
Как сделать, чтобы при получении, например, двух запросов в FastAPI, они выполнялись независимо друг от друга. То есть, если первым придет запрос на обработку, который занимает 20 сек, а вторым придет запрос, который занимает 5 сек, то результат второго запроса возвращался раньше первого? В данный момент имеется такой пример (в async функциях yield/thread/multiproccessing не присутствуют):
if __name__ == "__main__":
uvicorn.run("main:app", host= host, port= port, log_level="info", loop="asyncio")
@app.post("/bar_logo")
async def add_bar_logo(pos: str = Form(""), fio: str = Form(""), phone: str = Form(""), date: str =
Form(""), file: UploadFile = File(...)):
file_data = file.file.read()
res_pdf = await bar_logo(file_data, pos, fio, phone, date)
return FileResponse(res_pdf, media_type="application/pdf", filename=file.filename)
async def bar_logo(file_data, pos, fio, phone, date):
uuid = uuid4()
doc = fitz.open(stream=file_data, filetype="pdf")
if doc[0].rect.width > doc[0].rect.height:
l_path = await draw_bar_w(pos, fio, phone, date)
with open(l_path, "rb") as f:
logo = fitz.Pixmap(f.read())
else:
l_path = await draw_bar_h(pos, fio, phone, date)
with open(l_path, "rb") as f:
logo = fitz.Pixmap(f.read())
doc[-1].insert_image(doc[-1].rect, pixmap=logo)
save_dir = f"../results/{uuid}.pdf"
doc.save(save_dir, garbage=2, deflate=True, encryption=encrypt_meth, permissions=perm, owner_pw="")
return save_dir
Ответы (2 шт):
Базовый пример, выполняется асинхронно.
import asyncio
import uvicorn as uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/one")
async def read_root():
await asyncio.sleep(10)
return {"first_root": "one"}
@app.get("/two")
async def read_item():
return {"second_root": "two"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Есть асинхронные фреймворки, есть синхронные. В целом идея такая: асинхронные по умолчанию выполняют все запросы в 1 потоке, синхронные выполняют каждый запрос в отдельном потоке. Если писать на async, то никаких доп задержек быть не должно, как cpu bound так и io bound без async.
async def - не значит, что функция выполняется асинхронно.
Посмотрев на ваш пример, можно увидеть что async def bar_logo - синхронно читает из файловой системы => io задержка, которая блочит весь event loop. Блокировка event loop = все клиенты ждут.
Вот и получается, что 1 запрос прилетел и так как там блочится event loop, все остальные запросы не могут быть обработаны. Это очень грубо, но в общем работает это примерно так.
Чтобы этого избежать нужно использовать специальные асинхронные либы, aiofiles например для чтения файлов.
А вот если либы специальной нет, то все io вещи нужно заворачивать в потоки. Можно присмотреться к anyio библиотеки, которая облегчит тут жизнь.
from anyio import to_thread
async def io_bound(io_fun, params_fun):
await to_thread.run_sync(io_fun, params_fun)
Потоки - хорошо подходят для io bound задач и плохо подходят для cpu bound задач, где требуются вычислительные ресурсы. Это связано с существованием GIL.
Поэтому для cpu bound задач можно использовать процессы.
Вот очень простой пример:
async def cpu_bound(fun_cpu_bound, params_fun):
with concurrent.futures.ProcessPoolExecutor() as executor:
return await loop.run_in_executor(executor, fun_cpu_bound, params_fun)