Можно ли объявить функцию обработчика маршрута в FastAPI как синхронную и как это повлияет на выполнение
Что проверяют
Знаете ли вы, как FastAPI на самом деле исполняет обычный def, или повторяете миф «синхронный код блокирует сервер». Следом почти всегда спрашивают, что будет, если вызвать time.sleep() или requests.get() внутри async def.
Как отвечать
Да, можно, и это штатный режим. Поведение зависит от того, как объявлена функция:
def: FastAPI не вызывает её в цикле событий, а отправляет в пул потоков черезrun_in_threadpoolиз Starlette (под капотомanyio.to_thread). Цикл событий остаётся свободным, блокирующий вызов занимает только поток;async def: функция выполняется прямо в цикле событий. Пока она ждёт наawait, воркер обслуживает другие запросы, но любой блокирующий вызов внутри останавливает весь процесс;- зависимости: то же правило действует для
Depends, синхронная зависимость тоже уходит в пул потоков.
Отсюда практическое правило. Если библиотека синхронная (requests, psycopg2, синхронная сессия SQLAlchemy), объявляйте обработчик через def. Если всё асинхронное (httpx.AsyncClient, asyncpg, AsyncSession), берите async def. Если в асинхронном обработчике нужен один блокирующий вызов, оберните его в await run_in_threadpool(...) или asyncio.to_thread(...).
Назовите и цену синхронного варианта: пул ограничен, по умолчанию у anyio 40 потоков на процесс. Когда все заняты медленными запросами, новые встают в очередь, и задержка растёт, хотя процессор простаивает. Лимит можно поднять через anyio.to_thread.current_default_thread_limiter(), но обычно лучше убрать саму блокировку. Для тяжёлых вычислений потоки не помогут из-за GIL: нужен ProcessPoolExecutor, несколько воркеров uvicorn или очередь задач.
Где спотыкаются
Говорят, что синхронный обработчик блокирует обработку остальных запросов. Для FastAPI это неверно, и интервьюер это сразу услышит.
Пишут async def «для скорости» и вызывают внутри синхронный драйвер базы. Под нагрузкой один медленный запрос замораживает весь воркер, и это хуже, чем честный def.
Считают пул потоков бесконечным. При сорока зависших вызовах к внешнему API сервис перестаёт отвечать, хотя загрузка CPU почти нулевая.