Перейти к содержимому

Как вызвать асинхронную функцию в синхронной 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(. ): 

Корутины в Python

Когда говорят «написать корутину», обычно подразумевают асинхронную функцию. Корутины можно ставить на паузу, чтобы дать другим функциям немного поработать. В этом заключается принцип асинхронности. О нём мы рассказывали в этой статье.

Давайте сразу рассмотрим пример асинхронной функции:

import asyncio async def count_to_three(): print("Веду отсчёт. 1") await asyncio.sleep(0) print("Веду отсчёт. 2") await asyncio.sleep(0) print("Веду отсчёт. 3") await asyncio.sleep(0) 

Очень похоже на обычную функцию, однако здесь есть два новых слова: async и await .

async говорит Питону о том, что мы пишем не просто функцию, а асинхронную функцию. Просто добавили async и всё, функция теперь асинхронная.

Второе слово — await. Оно прерывает исполнение функции, и возвращает управление программой наружу. После этого корутину можно запустить повторно, а затем еще и еще, и каждый раз она будет продолжать работу с того await , на котором прервалась ранее. Например, в функции count_to_three команда await встречается три раза, значит корутину можно вызвать четыре раза (да, не три!). Корутина будет работать до первого await, затем до второго, до третьего и на четвёртый раз выполнит остатки до конца.

Нельзя делать await None или await «Hello, World!» . Можно await только то, что так и называют — «awaitable».

await asyncio.sleep(0) — это команда корутине «Дай поработать другим!»

Сразу покажем, как это выглядит на практике:

coroutine_counter = count_to_three() print(coroutine_counter) # coroutine_counter.send(None) # Выведет "Веду отсчёт. 1" coroutine_counter.send(None) # Выведет "Веду отсчёт. 2" coroutine_counter.send(None) # Выведет "Веду отсчёт. 3" 

Мы вызываем асинхронную функцию count_to_three , однако она не выводит на экран цифру 1, а возвращает корутину. Все асинхронные функции так делают. Это сделано для того, чтобы у вас был объект этой корутины в переменной. Теперь корутину можно запускать раз за разом, а она раз за разом будет делать кусочек и останавливаться на следующем await .

Чтобы запустить корутину, используют метод send() . При каждом запуске корутины этим методом она продолжает исполняться с последнего await , на котором она остановилась. Поэтому при новом запуске той же корутины срабатывает не тот же print , а следующий.

Нельзя просто .send() . Всегда нужно передавать какое-то значение. Об этом тоже расскажем позже. Пока что воспринимайте .send(None) как команду «продолжи выполнять корутину».

Когда корутина закончится?

Она остановится навсегда, когда закончатся все await или встретится return . Когда корутина заканчивается — она истощается и вызов .send() выдаёт ошибку:

coroutine_counter = count_to_three() coroutine_counter.send(None) # Выведет "Веду отсчёт. 1" coroutine_counter.send(None) # Выведет "Веду отсчёт. 2" coroutine_counter.send(None) # Выведет "Веду отсчёт. 3" coroutine_counter.send(None) # Выбросит ошибку StopIteration 

Если мы хотим запустить наш счётчик сначала, придётся создать новую корутину, вызвав count_to_three() :

coroutine_counter = count_to_three() coroutine_counter.send(None) # Выведет "Веду отсчёт. 1" coroutine_counter.send(None) # Выведет "Веду отсчёт. 2" coroutine_counter_new = count_to_three() coroutine_counter_new.send(None) # Снова выведет "Веду отсчёт. 1", новая корутина 

Обычно заранее не известно сколько await будет до момента «истощения», поэтому исключение приходится «перехватывать»:

coroutine_counter = count_to_three() while True: try: coroutine_counter.send(None) # В четвёртый раз здесь вылетит StopIteration except StopIteration: break 

Исключение StopIteration возникает всего один раз. Если после него попробовать запустить корутину ещё раз, то поднимется другое исключение — RuntimeError , и оно уже будет считаться ошибкой. О том как работать с исключениями читайте в статье про try except.

Нельзя запускать истощённую корутину.

Добиваемся асинхронности

С корутинами разобрались, останавливать их научились. А зачем.

Корутины позволят вашему коду работать асинхронно, т.е. делать несколько вещей одновременно. Допустим, вы решили скачать несколько файлов. Обычный, синхронный код скачивает файлы по-очереди. Сначала первый файл целиком, затем второй, тоже целиком. Асинхронный код качает файлы одновременно, по кусочкам. Приведём пример скачивания двух файлов:

async def download_file(url): # здесь происходит какая-то логика со скачиванием файла image_downloader = download_file('https://www.some-images.com/image1.jpg') music_downloader = download_file('https://www.music-site.com/artist/album/song5.mp3') coroutines = [music_downloader, image_downloader] while True: for coroutine in coroutines.copy(): try: coroutine.send(None) except StopIteration: coroutines.remove(coroutine) if len(coroutines) == 0: break 

Разберём как работает код:

  1. Мы создали 2 корутины: image_downloader и music_downloader . Первая качает картинку по ссылке https://www.some-images.com/image1.jpg , вторая — музыку по ссыке https://www.music-site.com/artist/album/song5.mp3 .
  2. Мы положили их в список coroutines
  3. В бесконечном цикле мы по очереди запускаем все корутины из списка. Если вышла ошибка StopIteration — корутина истощилась, т.е. файл скачан. Убираем её из списка, корутина больше запускаться не будет.
  4. Чтобы итерация по списку coroutines не сбивалась после удаления элемента из него итерируем не по оригиналу, а по копии coroutines.copy() .
  5. Если список с корутинами закончился (его длина равна нулю), пора заканчивать и бесконечный цикл, потому что все файлы скачаны.

Передать параметры в асинхронную функцию

В плане аргументов асинхронные функции ничем не отличаются от обычных. Доработаем пример со счетчиком и вместо async def count_to_three напишем универсальную функцию async def count :

import asyncio async def count(limit=3): for step in range(1, limit+1): print("Веду отсчёт.", step) await asyncio.sleep(0) coroutine = count(5) while True: coroutine.send(None) 
Веду отсчёт. 1 Веду отсчёт. 2 Веду отсчёт. 3 Веду отсчёт. 4 Веду отсчёт. 5 Traceback (most recent call last): File "", line 2, in StopIteration 

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

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

Я первый раз столкнулся с созданием потоков в пайтон. Я пишу бота для тг с помощью библиотеки 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) Как лучше именовать новые потоки которые будут создаваться в процессе?

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

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

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

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

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

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

Комментировать
Решения вопроса 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 по документации или публикациям-гайдам.

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

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