3.1 Модуль asyncio
Свои потоки для асинхронных подзадач уже давно никто не создаёт. Вернее, создавать их можно, но такие действия считаются слишком низкоуровневыми и используются лишь разработчиками фреймворков. И то, когда без них совсем не обойтись.
Сейчас в моде асинхронное программирование, операторы async/await и корутины с тасками. Но обо всём по порядку…
Немного истории
Изначально в Python для решения задач асинхронного программирования использовались корутины, основанные на генераторах. Потом, в Python 3.4, появился модуль asyncio (иногда его название записывают как async IO), в котором реализованы механизмы асинхронного программирования. В Python 3.5 появилась конструкция async/await.
А теперь немного вводной информации. Сначала я расскажу кратко про все эти вещи, потом подробнее, а затем ещё подробнее. По-другому не получится, так как почти все они работают в связке, и детально объяснить работу одной сущности без ссылок на другие не выйдет.
Модуль asyncio
Модуль asyncio предназначен для написания асинхронных программ, обеспечивая возможность выполнения задач параллельно. Он поддерживает асинхронные операции ввода-вывода, таймеры, сокеты, выполнение корутин и многопоточность, работая в одном или нескольких циклах событий.
Корутины (Coroutines)
Корутины — это асинхронные функции, определяемые с помощью ключевого слова async def. Корутины позволяют приостанавливать своё выполнение с помощью ключевого слова await, что позволяет другим корутинам выполняться в это время.
import asyncio
# Определение асинхронной функции (корутины)
async def main():
print('Hello ...')
# Приостанавливаем выполнение на 1 секунду
await asyncio.sleep(1)
print('... World!')
# Запуск асинхронной функции main() в цикле событий
asyncio.run(main())
Цикл событий (Event Loop)
Цикл событий управляет выполнением корутин, задач и других асинхронных операций. Вызов asyncio.run() запускает цикл событий и выполняет корутину до завершения.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# Получение текущего цикла событий
loop = asyncio.get_event_loop()
# Запуск корутины до завершения
loop.run_until_complete(main())
# Закрытие цикла событий после завершения всех задач
loop.close()
Задачи (Tasks)
Задачи позволяют запускать корутины параллельно. Создаются с помощью asyncio.create_task() или asyncio.ensure_future().
import asyncio
# Определение корутины, которая будет выполнена с задержкой
async def say_after(delay, what):
# Приостанавливаем выполнение на заданное время
await asyncio.sleep(delay)
print(what)
# Основная корутина
async def main():
# Создаем задачи для параллельного выполнения корутин
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# Ждем завершения обеих задач
await task1
await task2
# Запуск основной корутины
asyncio.run(main())
Фьючерсы (Futures)
Объекты Future представляют собой результаты асинхронных операций, которые будут доступны в будущем. Например, они используются для ожидания завершения асинхронной задачи.
import asyncio
# Определение корутины, которая имитирует долгую задачу
async def long_running_task():
print('Task started')
# Приостанавливаем выполнение на 3 секунды
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# Основная корутина
async def main():
# Создаем фьючерс для ожидания завершения задачи
future = asyncio.ensure_future(long_running_task())
# Ждем завершения задачи и получаем результат
result = await future
print(f'Task result: {result}')
# Запуск основной корутины
asyncio.run(main())
3.2 Асинхронная функция — async def
Асинхронная функция объявляется так же, как и обычная, только перед ключевым словом def нужно написать слово async.
async def ИмяФункции(параметры):
код функции
Асинхронная функция объявляется как обычная, вызывается как обычная, но вот результат она возвращает другой. Если вызвать асинхронную функцию, то она вернёт не результат, а специальный объект — корутину.
Можно даже это проверить:
import asyncio
async def main():
print("Hello World")
# Вызов асинхронной функции, который возвращает корутину
result = main()
# Проверяем тип результата
print(type(result)) # <class 'coroutine'>
Что же происходит? Когда вы помечаете функцию словом async, то фактически добавляете к ней декоратор, который делает примерно это:
def async_decorator(func):
# Создаем объект Task
task = Task()
# Передаем в него нашу функцию func, чтобы он её выполнил
task.target = func
# Добавляем объект task в очередь задач — Event Loop
eventloop.add_task(task)
# Возвращаем объект task
return task
А ваш код становится похож на:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
Смысл данной аналогии следующий:
Когда вы вызываете асинхронную функцию, создаётся специальный объект Task, который будет выполнять вашу функцию, но когда-то в будущем. Может через 0.0001 секунды, а может и через 10.
Этот объект task вам сразу и возвращается как результат вызова вашей асинхронной функции. Это и есть корутина. Ваша асинхронная функция, возможно, вообще ещё не начала выполняться, а объект task (корутина) у вас уже есть.
Зачем вам этот task (корутина)? Вы мало что можете с ним сделать, но возможно сделать 3 вещи:
- Подождать, пока асинхронная функция выполнится.
- Подождать, пока асинхронная функция закончит выполняться и получить из корутины результат выполнения функции.
- Подождать, пока выполнится 10 (любое число) асинхронных функций.
Как это сделать, я расскажу ниже.
3.3 Оператор await
Большая часть действий с корутиной начинается с «подождать выполнения асинхронной функции». Поэтому для этого действия у нас есть специальный оператор await.
Вам нужно просто написать его перед корутиной:
await корутина
Или сразу перед вызовом асинхронной функции:
await асинхронная_функция(аргументы)
Когда Python встречает в коде оператор await, он приостанавливает выполнение текущей функции и ждёт, пока корутина не выполнится — пока не завершится асинхронная функция, на которую ссылается корутина.
Важно! Оператор await используется только внутри асинхронной функции для приостановки выполнения до тех пор, пока не завершится другая корутина или асинхронная операция.
Это делается для того, чтобы упростить процесс переключения между вызовами асинхронных функций. Такой вызов await — это фактически декларация «мы тут будем ждать неизвестно сколько — займитесь выполнением других асинхронных функций».
Пример:
import asyncio
# Определение асинхронной функции
async def async_print(text):
print(text)
# Основная асинхронная функция
async def main():
# Используем await для ожидания выполнения асинхронной функции
await async_print("Hello World")
# Запуск основного цикла событий и выполнение корутины main()
asyncio.run(main()) # запускает асинхронную функцию
На самом деле оператор await работает ещё хитрее — он также возвращает результат выполнения асинхронной функции, у которой он был вызван.
Пример:
import asyncio
# Определение асинхронной функции, которая складывает два числа
async def async_add(a, b):
return a + b
# Основная асинхронная функция
async def main():
# Используем await для получения результата выполнения async_add
sum = await async_add(100, 200)
print(sum)
# Запуск основного цикла событий и выполнение корутины main()
asyncio.run(main()) #запускает асинхронную функцию
Итак, подведём итоги, оператор await:
- Приостанавливает текущую асинхронную функцию до тех пор, пока не завершится другая корутина или асинхронная операция.
- Возвращает результат выполнения асинхронной операции или корутины.
- Можно использовать только внутри асинхронной функции.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ