Как запустить асинхронную задачу и сразу вернуть ответ клиенту
Что проверяют
Различаете ли вы «выполнить после ответа в том же процессе» и «поставить задачу в надёжную очередь». Интервьюер ждёт, что вы спросите про длительность задачи и цену её потери. Следом обычно спрашивают, как клиент узнает результат и что будет с задачей при перезапуске сервиса.
Как отвечать
Выбор зависит от задачи:
BackgroundTasksв FastAPI: функция выполнится после отправки ответа, в том же процессе. Синхронная функция уйдёт в пул потоков, асинхронная в event loop. Годится для коротких действий вроде записи в лог или отправки письма;asyncio.create_task: тоже внутри процесса, но результат нужно сохранить в переменную или множество: цикл событий держит на задачи только слабые ссылки, и незащищённая задача может исчезнуть посреди работы;- очередь задач: Celery, RQ, Dramatiq, Taskiq или arq с брокером Redis или RabbitMQ. Задача переживает перезапуск веб-процесса, её можно повторить, ограничить по времени, а воркеры масштабировать отдельно.
Контракт с клиентом: вернуть 202 Accepted и идентификатор задачи, дать эндпоинт статуса GET /tasks/{id} или прислать результат вебхуком, через WebSocket или SSE. В Django задачу ставят в очередь внутри transaction.on_commit, иначе воркер начнёт работу раньше, чем данные зафиксированы.
Короткий пример на FastAPI:
@app.post("/reports", status_code=202)
async def create_report(bg: BackgroundTasks):
report_id = uuid4().hex
bg.add_task(build_report, report_id)
return {"id": report_id}Где спотыкаются
Выполняют тяжёлую синхронную работу внутри async def. Вызовы time.sleep, requests.get или расчёт на CPU блокируют весь event loop, и сервис перестаёт отвечать другим клиентам.
Отдают фоновым задачам процесса долгую и важную работу. При деплое или падении воркера uvicorn задача пропадает без следа, повтора не будет.
Отвечают 200 без идентификатора. Клиент не может узнать, закончилась ли задача, и начинает повторять запрос, создавая дубли. Для такого случая нужен ещё и ключ идемпотентности в запросе.