Программирование с ИИ
ENRU
2.10

Асинхронность

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.text

requests синхронный: на время запроса встаёт вся программа, и бот перестаёт отвечать всем. Асинхронные замены: 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.