Как вызвать асинхронную функцию в синхронной python
Перейти к содержимому

Как вызвать асинхронную функцию в синхронной python

  • автор:

Как вызвать асинхронную функцию?

При простом вызове get_app() я получаю генератор, если использовать await , то выдаёт ошибку SyntaxError: ‘await’ outside function , если делать вызов через asyncio.run(get_app()) выдаёт ошибку RuntimeError: asyncio.run() cannot be called from a running event loop . p.s. хочу вернуть значение app_info

Отслеживать
задан 20 окт 2020 в 21:12
23 1 1 серебряный знак 3 3 бронзовых знака

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Судя по ошибкам, Вы запускаете get_app из синхронного колбэка асинхронного сервера. Иначе работало либо через первый, либо черезвторой способы.

Из колбэка получить результат можно только в новый колбэк. привет яваскрипт 😉

def second_cb(fut): app_info = fut.result() продолжение кода def server_cb(request): asyncio.ensure_future(get_app()).add_done_callback(second_cb) return 

second_cb выполнится после завершения server_cb.

Чтоб избежать callback-hell перевызывай server_cb как асинхронную функцию и делай там await-ы

def server_cb(request): async def server_cb_async(request): r = await get_app() r2 = await another(r) # вся обработка вложенна тут. asyncio.ensure_future(server_cb_async(request)).add_done_callback(lambda x: pass) 

или запустить асинхронный вариант как таск

 asyncio.get_running_loop().create_task(server_cb_async(request)) 

В последнее время я через таски делаю.

Как альтернативный вариант можно использовать готовую джанговскую async-to-sync

from asgiref.sync import async_to_sync res = async_to_sync(get_app()) 
@async_to_sync async def get_app(. ): 

Как вызвать асинхронную функцию из обычной функции?

Но когда я вызываю функцию main программа не заходит в эту функцию в другом классе, а создаёт объект coroutine object MainTelegram.main . Я почитал документацию по этому объекту, но так и не понял, как с ним работать. Из этого и мой вопрос. Как мне вызвать асинхронную функцию из обычной функции и возможно ли это вообще?

  • Вопрос задан более трёх лет назад
  • 6070 просмотров

Комментировать
Решения вопроса 1

gscraft

Программист, философ
Вам придется идти асинхронно от корня, любой async сопровождается await (и наоборот):

import asyncio async def main(): await . # вызов библиотек if __name__ == "__main__": loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) # передайте точку входа finally: # действия на выходе, если требуются pass

Да, стоит разобраться с работой asyncio по документации или публикациям-гайдам.

Асинхронность python на примере

Хороший реальный пример сравнения приложений с синхронизацией и асинхронностью — это официант и повар в оживленном ресторане. Официант принимает заказы и выдает, а повар готовит еду.

Напишем функции повара и официанта, используя традиционный синхронный Python-код. Вот как он будет выглядеть:

 
import time

def waiter():
cook('Паста', 8)
cook('Салат Цезарь', 3)
cook('Отбивные', 16)

def cook(order, time_to_prepare):
print(f'Новый заказ: ')
time.sleep(time_to_prepare)
print(order, '- готово')

if __name__ == '__main__':
waiter()

Сохраним файл sync.py .

Здесь повар симулируется в виде функции. Он принимает заказ и время на его приготовление. Затем с помощью функции time.sleep симулируется сам процесс готовки. А по завершении выводится сообщение о том, что заказ готов.

Есть еще одна функция официанта. По внутренней логике официант принимает заказы от посетителей и синхронно передает их повару.

Убедитесь, что установлена версия Python 3.7+ с помощью команды python3 --version на Mac или python --version — на Windows. Если версия меньше 3.7, обновите.

Запустите пример, чтобы убедиться, что все заказы медленно, но верно подаются.

Новый заказ: Паста Паста - готово Новый заказ: Салат Цезарь Салат Цезарь - готово Новый заказ: Отбивные Отбивные - готово

Первая асинхронная программа

Теперь конвертируем программу так, чтобы она использовала библиотеку asyncio. Это будет первый шаг для того, чтобы разобраться с тем, как писать асинхронный код. Скопируем файл sync.py в новый файл coros.py со следующим кодом:

 
import asyncio
import time

async def waiter() -> None:
cook('Паста', 8)
cook('Салат Цезарь', 3)
cook('Отбивные', 16)

async def cook(order: str, time_to_prepare: int) -> None:
print(f'Новый заказ: ')
time.sleep(time_to_prepare)
print(order, '- готово')

asyncio.run(waiter())

В первую очередь нужно импортировать стандартную библиотеку Python под названием asyncio . Это нужно для получения асинхронных возможностей.

В конце программы заменим if __name__ == '__main__' на новый метод run из модуля asyncio . Что именно делает run ?

По сути, run берет низкоуровневый псевдо-сервер asyncio, который называется рабочим циклом. Этот цикл является координатором, который следит за приостановкой и возобновлением задач из кода. В примере с поваром и официантом вызов «cook(»Паста’)» — это задача, которая выполнится, но также будет приостановлена на 8 секунд. Таким образом после получения запроса он отмечается, а программа переходит к выполнению следующего. После завершения заказа на приготовление пасты цикл продолжит выполнение на следующей строке, где готовится салат «Цезарь».

Команде run нужна функция, которую она будет выполнять, поэтому передаем waiter , которая является основной функцией в этом коде.

Run также отвечает за очистку, поэтому когда весь код проработает, он отключится от цикла.

Однако этих изменений недостаточно, чтобы код был асинхронным. Нужно сообщить asyncio, какие функции и задачи будут работать асинхронно. Поэтому поменяем функцию waiter , чтобы она выглядела следующим образом.

 
async def waiter() -> None:
await cook('Паста', 8)
await cook('Салат Цезарь', 3)
await cook('Отбивные', 16)

Функция waiter объявляется асинхронной за счет добавления приставки async в начале. После этого появляется возможность сообщать asyncio, какие из задач будут асинхронными внутри. Для этого к ним добавляется ключевое слово await .

Такой код можно читать следующим образом: «вызвать функцию cook и дождаться ( await ) ее результата, прежде чем переходить к следующей строке». Но это не процесс с блокировкой потока. Наоборот, он сообщает циклу следующее: «если есть другие запросы, можешь переходить к их выполнению, пока мы ждем, а мы дадим знать, когда текущий запрос завершится».

Достаточно лишь запомнить, что если есть задачи с await , то сама функция должна быть объявлена с async .

А как же функция cook ? Ее тоже нужно сделать асинхронной, поэтому перепишем ее вот так.

 
async def cook(order, time_to_prepare):
print(f'Новый заказ: ')
await time.sleep(time_to_prepare)
print(order, '- готово')

Но здесь есть одна проблема. Если использовать стандартную функцию time.sleep , то она заблокирует весь процесс выполнения, сделав асинхронную программу бесполезной. В этом случае нужно использовать функцию sleep из модуля asyncio.

 
async def cook(order, time_to_prepare):
print(f'Новый заказ: ')
await asyncio.sleep(time_to_prepare)
print(order, '- готово')

Это гарантирует, что пока функция cook находится в состоянии сна до завершения таймера, программа сможет начать выполнение других запросов.

Если сейчас запустить программу, то результат будет таким:

Новый заказ: Паста Паста - готово Новый заказ: Салат Цезарь Салат Цезарь - готово Новый заказ: Отбивные Отбивные - готово

Но разницы с асинхронной версией нет. Возможно, казалось, что этот вариант исполнится быстрее. На самом деле, это одно из главных заблуждений касательно асинхронного кода. Некоторые ошибочно считают, что он работает быстрее. Но это программа уже лучше, хотя этого нельзя явно сказать на основе опыта работы с ней.

Если запустить эту программу как часть сайта, то будет возможность обслуживать сотни тысяч посетителей одновременно на одном сервере без каких-либо проблем. Когда вместо этого использовать синхронный код, то максимум можно рассчитывать на пару десятков пользователей. Если же их будет больше, то процессор сервера не сможет выдерживать нагрузку.

Сопрограммы и задачи (coroutines and tasks)

Сопрограммы (coroutines)

Функции waiter и cook трансформируются именно в тот момент, когда перед их определением ставится ключевое слово async . С этого момент их можно считать сопрограммами.

Если попытаться запустить одну из таких прямо, то вернется сообщение с информацией о ней, но сама программа не будет запущена. Попробуем запустить терминал Python и импортировать туда функцию cook из файла coros . Во-первых, нужно закомментировать команду asyncio.run так, чтобы код не выполнялся. После этого файл можно сохранить.

# asyncio.run(waiter())

Затем откроем терминал и сделаем следующее:

Как вызвать асинхронную функцию с синхронной функции?

Я первый раз столкнулся с созданием потоков в пайтон. Я пишу бота для тг с помощью библиотеки aiogram, внутри одной асинхронной функции я создаю новый поток что бы выполнить сторонний код и не заставлять основной поток ожидать. Так выходит что после выполнения стороннего кода мне надо отправить сообщение юзеру в тг, а отправка сообщение подразумевает под собой вызов асинхронной функции, то есть мне нужно теперь с нового синхронного потока вызвать асинхронный. Я достаточного много читал на форумах различных ответов, но я так и не пришел к решению.
Вот мой код:

async def load_link(message : types.Message, state: FSMContext): await state.finish() thr = threading.Thread(target=run_test, args=(data, message), name='thr-1').start() def run_test(data, message): main.main(data[0]['surname'], data[0]['email'], data[0]['time'], message.text) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(result(message)) loop.close() async def result(message): document = types.InputFile('Result.jpg') await bot.send_document(chat_id=message.from_user.id, document=document, caption='Тест пройден ', reply_markup=kb_start_client) os.remove('Result.jpg')

Данный код возвращает ошибку: RuntimeError: Timeout context manager should be used inside a task
У меня вопросы такого характера (прошу извиниться за возможную тупость но я новичок в этом):
1) Можно ли как то реализовать то что я написал выше или как можно это упростить и сделать более рациональным?
2) Если я вызываю асинхронную функцию с синхронной я правильно понимаю что мне не надо ставить Lock или Rlock, потому что асинхронная функция и так будет выполняться одним потоком или это не так?
3) И возможно ли вообще создавать новые потоки асинхронными (я нигде не видел что бы так можно было делать, но это бы упростило мою задачу)
4) Как лучше именовать новые потоки которые будут создаваться в процессе?

  • Вопрос задан более года назад
  • 543 просмотра

7 комментариев

Простой 7 комментариев

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *