Асинхронность в Python (бот на aiogram) – некоторые задачи подвешивают бота на время. Почему?
Друзья, привет.
Есть VPN. Через API можно создавать ключи, считать ключи на сервере и т.п.
Пишу бота (aiogram). Он должен быть асинхронным. По идеи. Но заметил такую штуку. Когда один пользователь обращается к серверу (например собирает статистику по ключам), то у другого пользователя на время бот замирает.
Почему так? Это ведь разные функции в боте, разные хендлеры. Почему одна задачка (одного юзера) подвешивает всего бота (задачки других юзеров)?
И как собственно это грамотно решить?
Пример подсчёта ключей:
client = OutlineVPN(api_url=server[0])
keys_amount = len(client.get_keys())
Улучшит ли ситуацию, если я сделаю функцию OutlineVPN асинхронной (async def ...) и буду её вызывать через await ...
Ответы (1 шт):
Синхронные функции блокируют основной поток, в это время не могут выполняться асинхронные функции. Асинхронность основана на том, что если асинхронная функция долго чего-то ждет, она не блокирует поток, а на время ожидания (вызов другой асинхронной функции через await) отдает управление циклу обработки событий (который уже передает управление другим асинхронным функциям), и только когда ожидание завершено (например, получен ответ от сервера), функция просыпается и дальше работает. Синхронная же функция ни про какие await не знает, и никогда не отдает управление асинхронным функциям.
- Проверьте, может быть есть возможность получить количество ключей без получения самих ключей. Возможно для этого действия время ожидания будет меньше, чем для физического получения всех ключей, и поток будет блокироваться на меньшее время. Но это скорее "костыль".
- Синхронная функция обернутая в асинхронную не станет сама по себе работать асинхронно, она все так же будет блокировать поток. Можно обернуть ее в
await asyncio.to_thread(доступно начиная с python 3.9) либо в конструкцию видаawait asyncio.get_running_loop().run_in_executor(None, func, *args)(func- ваша синхронная функция), чтобы эта функция выполнялась в отдельном потоке, не блокируя основной поток.
Пример на вашем коде:
import asyncio
try:
from asyncio import to_thread # added in Python 3.9
except ImportError:
async def to_thread(func, *args):
return await asyncio.get_running_loop().run_in_executor(None, func, *args)
...
async def func():
client = OutlineVPN(api_url=server[0])
keys = await to_thread(client.get_keys)
keys_amount = len(keys)
Если инициализация клиента тоже долгая операция, ее тоже придется обернуть в to_thread.
Ссылки:
asyncio.to_thread- awaitable
loop.run_in_executor(executor, func, *args) - пример оборачивания синхронного API клиента в асинхронную обертку с помощью описанного в этом ответе подхода: transifex_api_2.py