В чем разница между синхронными и асинхронными обработчиками маршрутов в FastAPI?

Ответ

Основное различие заключается в том, как они обрабатывают блокирующие операции ввода-вывода (I/O) и как это влияет на производительность сервера.

Синхронный обработчик (def)

Это обычная функция Python. Когда она выполняет блокирующую операцию (например, запрос к базе данных, чтение файла, HTTP-запрос с помощью requests), она останавливает выполнение и ждет результата.

Чтобы это не блокировало весь сервер, FastAPI выполняет синхронные функции в отдельном пуле потоков (thread pool). Это позволяет основному циклу событий (event loop) продолжать обрабатывать другие запросы, пока текущий ожидает в своем потоке.

Когда использовать:

  • Для CPU-bound задач (тяжелые вычисления).
  • При использовании библиотек, у которых нет async версии (например, стандартные ORM или requests).

Пример:

import time
from fastapi import FastAPI

app = FastAPI()

@app.get("/sync-route")
def sync_route():
    # Блокирующая операция: поток будет "спать" 2 секунды
    time.sleep(2)
    return {"message": "Done sleeping!"}

Асинхронный обработчик (async def)

Это корутина (coroutine), которая выполняется непосредственно в цикле событий. Когда она встречает await на I/O-операции (например, await async_db.fetch()), она передает управление обратно циклу событий, который может в это время выполнять другие задачи. После завершения I/O-операции выполнение корутины возобновляется.

Это более эффективный способ обработки множества одновременных I/O-запросов, так как не требует создания отдельных потоков для каждого.

Когда использовать:

  • Для I/O-bound задач (работа с сетью, базами данных, файловой системой).
  • При использовании async-совместимых библиотек (httpx, asyncpg, aiofiles).

Пример:

import asyncio
from fastapi import FastAPI

app = FastAPI()

@app.get("/async-route")
async def async_route():
    # Неблокирующая операция: цикл событий не блокируется
    await asyncio.sleep(2)
    return {"message": "Done sleeping asynchronously!"}

Ключевой вывод

  • Используйте async def для операций ввода-вывода с async-библиотеками для максимальной производительности.
  • Используйте def для процессорных задач или при работе с блокирующими библиотеками. FastAPI позаботится о том, чтобы они не блокировали сервер.

Ответ 18+ 🔞

А, слушай, тут народ часто путается, какую хуйню писать в роутах — def или async def. Ёпта, а разница-то проще, чем кажется, но от неё пиздец как зависит, как сервер будет ебашить.

Синхронный обработчик (def)

Ну, это обычная функция, как в старые добрые времена. Представь: она делает запрос к базе данных или читает файл — и на этом месте она встаёт как вкопанная и ждёт, пока эта операция не выполнится. Прям как мудак в очереди за сосиской в тесте.

Чтобы из-за одного такого тормоза не встал весь сервер, FastAPI берёт и кидает эту функцию в отдельный поток (thread pool). Пока один поток спит, главный цикл событий (event loop) может спокойно обслуживать других клиентов. Хитро, да?

Когда это юзать:

  • Когда у тебя задачи, которые жрут процессор (CPU-bound) — типа сложных вычислений.
  • Когда ты используешь библиотеки, которые нихуя не умеют в асинхронность (например, старый добрый requests или многие ORM).

Пример, чтобы было понятно:

import time
from fastapi import FastAPI

app = FastAPI()

@app.get("/sync-route")
def sync_route():
    # Вот тут поток просто уснёт на 2 секунды, как сурок
    time.sleep(2)
    return {"message": "Done sleeping!"}

Асинхронный обработчик (async def)

А вот это уже корутина, мать её. Она работает прямо в цикле событий. Когда она доходит до операции ввода-вывода и видит await (например, await async_db.fetch()), она не тупо ждёт, а говорит: «Окей, я пока пошла, разбудите, когда данные придут». И цикл событий в это время может заниматься другими делами. Эффективность, блядь, зашкаливает!

Когда это юзать:

  • Для всех операций, где ты ждёшь ответа от кого-то (I/O-bound) — запросы в сеть, базы, файлы.
  • Когда у тебя есть асинхронные библиотеки (httpx, asyncpg, aiofiles).

Пример для ясности:

import asyncio
from fastapi import FastAPI

app = FastAPI()

@app.get("/async-route")
async def async_route():
    # Тут цикл событий не блокируется, он пойдёт делать другие дела
    await asyncio.sleep(2)
    return {"message": "Done sleeping asynchronously!"}

Так что же в итоге, ёпта?

  • async def — твой выбор, когда нужно ебашить кучу I/O-операций и есть подходящие библиотеки. Производительность будет овердохуищная.
  • def — используй для тяжёлых вычислений или когда библиотека только синхронная. FastAPI сам разберётся и не даст ей заблокировать всё нахуй.

Вот и вся магия, никакой ебалы с мантрами.