Асинхронность
async, await и зачем это боту
Что это
Асинхронность — способ не простаивать, пока программа чего-то ждёт. Запрос к API идёт полсекунды, и всё это время обычный код просто стоит. Асинхронный — успевает заняться другими делами.
Для бота это принципиально: пока один пользователь ждёт погоду, остальные
должны получать ответы. Для чтения кода достаточно понять два слова: async
и await.
Как выглядит в коде
import asyncio
import time
async def fetch_city(name: str) -> str:
await asyncio.sleep(0.3) # как будто запрос в сеть
return f"{name}: +4"
async def main() -> None:
t = time.perf_counter()
for city in ["Oslo", "Bergen", "Tromsø"]:
print(await fetch_city(city))
print(f"по очереди: {time.perf_counter() - t:.1f} c")
t = time.perf_counter()
results = await asyncio.gather(
*(fetch_city(c) for c in ["Oslo", "Bergen", "Tromsø"])
)
print(f"разом: {time.perf_counter() - t:.1f} c")
asyncio.run(main())Вывод:
по очереди: 0.9 c
разом: 0.3 cТри запроса по 0,3 секунды: по очереди — 0,9, одновременно — 0,3. Это и есть вся польза асинхронности, измеренная.
Три слова
async def — функция асинхронная. Главное следствие: при вызове она не
выполняется, а возвращает объект-обещание. Чтобы получить результат, нужен
await.
await — «дождись результата, а пока ждём, займись другими делами».
Ставится только внутри async def.
asyncio.run(main()) — запуск. Точка, с которой начинается асинхронный мир.
Где выигрыш, а где нет
Асинхронность помогает там, где программа ждёт: сеть, диск, база. Там, где программа считает, выигрыша нет — процессор занят, и переключаться не на что.
| Помогает | Не помогает |
|---|---|
| запросы к API | перемножение матриц |
| работа с базой | обработка картинок |
| чтение и запись файлов | разбор большого текста |
| ожидание сообщений от Telegram | любые тяжёлые вычисления |
Самая частая ошибка — забытый await
data = fetch_city("Oslo") # забыли await
print(data)Выведет что-то вроде <coroutine object fetch_city at 0x...> и предупреждение
coroutine was never awaited. Функция не выполнилась вообще.
Блокирующий вызов в асинхронном коде
Вторая частая беда — обычная синхронная библиотека внутри async def:
async def fetch_city(name: str) -> str:
r = requests.get(url) # блокирует всё!
return r.textrequests синхронный: на время запроса встаёт вся программа, и бот перестаёт
отвечать всем. Асинхронные замены: aiohttp, httpx в асинхронном режиме.
То же с базой: sqlite3 блокирует, aiosqlite — нет.
Признак при чтении: внутри async def вызов без await у чего-то, что ходит
в сеть, на диск или в базу.
Как сказать ИИ
Есть ли в этом асинхронном коде блокирующие вызовы?
Назови строки и чем их заменить.Эти три запроса выполняются по очереди или одновременно?
Если по очереди — покажи, как сделать одновременно.Перепиши этот код так, чтобы он не блокировал цикл событий.
Библиотеки менять можно, но скажи, какие добавляешь.На что смотреть
Забытый await. Самая частая. Ищется по coroutine в выводе.
Синхронная библиотека внутри async def. requests, time.sleep,
обычный open на большом файле — всё это останавливает программу целиком.
time.sleep(1) надо заменять на await asyncio.sleep(1).
Последовательные await там, где можно разом. Цикл с await внутри
выполняет запросы по очереди. Если они независимы, их объединяют через
asyncio.gather — как в примере, втрое быстрее.
Асинхронность там, где не нужна. Если программа ничего не ждёт,
async только добавляет сложности. ИИ иногда делает асинхронным всё подряд.
Шпаргалка
async defне выполняется при вызове — нуженawait.awaitработает только внутриasync def.Польза там, где программа ждёт: сеть, диск, база. При вычислениях — нет.
<coroutine object ...>— забытawait.requestsиtime.sleepвнутриasync defблокируют всё.Независимые запросы объединяй через
asyncio.gather.