Async & Background Tasks
FastAPI поддерживает и def, и async def эндпоинты. Разница важна.
sync vs async
| Тип | Когда использовать |
|---|---|
def |
CPU-bound или I/O-bound через синхронные либы (psycopg2, requests) |
async def |
I/O через async-либы (httpx, asyncpg, redis.asyncio) |
Микс опасен: если внутри async def вызвать блокирующий вызов — фреймворк
не отрубит его, и весь event loop встанет.
# ПЛОХО — блокирует event loop
@app.get("/slow")
async def slow():
requests.get("https://example.com") # синхронный I/O!
return {}
# ХОРОШО
@app.get("/slow")
async def slow():
async with httpx.AsyncClient() as client:
await client.get("https://example.com")
return {}BackgroundTasks
Запустить что-то после ответа клиенту (без ожидания):
from fastapi import BackgroundTasks
def send_email(to: str):
smtp.send(to) # блокирующий I/O — ОК, выполняется после ответа
@app.post("/signup")
async def signup(bg: BackgroundTasks, email: str):
bg.add_task(send_email, email)
return {"status": "ok"}Подходит для: email, метрики, логирование. Не подходит для задач, которые должны пережить рестарт приложения.
Lifespan
Одноразовая инициализация/очистка ресурсов на старте/остановке приложения:
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.pool = await asyncpg.create_pool(DSN)
yield
await app.state.pool.close()
app = FastAPI(lifespan=lifespan)Пул, redis-клиент, kafka-продюсер — всё что должно жить весь процесс.
Когда что
- Один запрос, без побочек →
async def - Фоновая работа после ответа →
BackgroundTasks - Долгоживущие воркеры → отдельный процесс/очередь (Celery, ARQ)
- Глобальный ресурс →
lifespan+app.state
Грабли
asyncio.create_task(...)без ссылки — может быть собран GC до завершенияBackgroundTasksкрутятся в том же процессе; падение приложения их убивает- Логирование внутри
async defчерезprintблокирует event loop