Корутины и задачи
В этом разделе описываются основные API asyncio для работы с корутинами и задачами.
- Корутины
- Awaitables
- Запуск программы asyncio
- Создание задач
- Ожидание
- Запуск задач одновременно
- Защита от отмены
- Таймауты
- Примитивы ожидания
- Планирование из других потоков
- Интроспекция
- Объект задачи
- Корутины на основе генераторов
Корутины
Корутины объявленные с помощью синтаксиса async/await — предпочтительный способ написания приложений asyncio. Например, следующий фрагмент кода (требует Python 3.7+) выводит “hello”, ждёт 1 секунду, а затем выводит “world”:
>>> import asyncio
>>> async def main():
... print('hello')
... await asyncio.sleep(1)
... print('world')
>>> asyncio.run(main())
hello
world
Обратите внимание, что просто вызов корутины не запланирует её выполнение:
>>> main() <coroutine object main at 0x1053bb7c8>
Для фактического запуска корутины asyncio предоставляет три основных механизма:
- Функция
asyncio.run()для запуска точечной функции «main()» (см. пример выше). -
Ожидание корутины. Следующий фрагмент кода выведет “hello” после ожидания 1 секунды, а затем выведет “world” после ожидания ещё 2 секунд:
import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") await say_after(1, 'hello') await say_after(2, 'world') print(f"finished at {time.strftime('%X')}") asyncio.run(main())Ожидаемый вывод:
started at 17:13:52 hello world finished at 17:13:55
-
Функция
asyncio.create_task()для одновременного запуска корутин как asyncioTasks.Давайте изменим вышеприведённый пример и запустим две
say_afterкорутины одновременно:async def main(): task1 = asyncio.create_task( say_after(1, 'hello')) task2 = asyncio.create_task( say_after(2, 'world')) print(f"started at {time.strftime('%X')}") # Wait until both tasks are completed (should take # around 2 seconds.) await task1 await task2 print(f"finished at {time.strftime('%X')}")Обратите внимание, что ожидаемый вывод теперь показывает, что фрагмент выполняется на 1 секунду быстрее, чем раньше:
started at 17:14:32 hello world finished at 17:14:34
Awaitables
Мы говорим, что объект является awaitable объектом, если его можно использовать в выражении await. Многие API asyncio разработаны для приема awaitables.
Существует три основных типа awaitable объектов: корутины, задачи и футуры.
Корутины
Python корутины являются awaitables и поэтому могут быть ожидаемыми другими корутинами:
import asyncio
async def nested():
return 42
async def main():
# Nothing happens if we just call "nested()".
# A coroutine object is created but not awaited,
# so it *won't run at all*.
nested()
# Let's do it differently now and await it:
print(await nested()) # will print "42".
asyncio.run(main())
Важно
В этой документации термин «корутина» может использоваться для двух тесно связанных концепций:
- функция корутины: функция
async def; - объект корутины: объект, возвращаемый при вызове функции корутины.
asyncio также поддерживает устаревшие генераторные корутины.
Задачи
Задачи используются для планирования корутин одновременно.
Когда корутина оборачивается в задачу с помощью функций, таких как asyncio.create_task(), корутина автоматически запланирована для скорого запуска:
import asyncio
async def nested():
return 42
async def main():
# Schedule nested() to run soon concurrently
# with "main()".
task = asyncio.create_task(nested())
# "task" can now be used to cancel "nested()", or
# can simply be awaited to wait until it is complete:
await task
asyncio.run(main())
Фьючеры
Future — это специальный низкоуровневый awaitable объект, который представляет конечный результат асинхронной операции.
Когда ожидается объект Future, это означает, что корутина будет ждать, пока Future не будет разрешен в другом месте.
Объекты Future в asyncio необходимы для использования кода на основе обратного вызова с async/await.
Обычно нет необходимости создавать объекты Future в коде приложения.
Объекты Future, иногда предоставляемые библиотеками и некоторыми API asyncio, можно ожидать:
async def main():
await function_that_returns_a_future_object()
# this is also valid:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)
Хороший пример низкоуровневой функции, возвращающей объект Future, — loop.run_in_executor().
Запуск программы asyncio
-
asyncio.run(coro, *, debug=False) -
Выполнить корутину coro и вернуть результат.
Эта функция запускает переданную корутину, позаботившись о управлении циклом событий asyncio и завершении асинхронных генераторов.
Эта функция не может быть вызвана, когда другой цикл событий asyncio работает в той же потоке.
Если debug
True, цикл событий будет запущен в отладочном режиме.Эта функция всегда создаёт новый цикл событий и закрывает его в конце. Она должна использоваться в качестве главной точки входа для программ asyncio и, желательно, вызываться только один раз.
Пример:
async def main(): await asyncio.sleep(1) print('hello') asyncio.run(main())Новая в версии 3.7.
Примечание
Исходный код для
asyncio.run()можно найти в Lib/asyncio/runners.py.
Создание задач
-
asyncio.create_task(coro, *, name=None) -
Оборачивает корутину coro в
Taskи планирует её выполнение. Возвращает объект Task.Если name не
None, оно устанавливается как имя задачи с помощьюTask.set_name().Задача выполняется в цикле, возвращённом функцией
get_running_loop().RuntimeErrorгенерируется, если в текущем потоке нет работающего цикла.Эта функция была добавлена в Python 3.7. До Python 3.7 можно было использовать низкоуровневую функцию
asyncio.ensure_future():async def coro(): ... # In Python 3.7+ task = asyncio.create_task(coro()) ... # This works in all Python versions but is less readable task = asyncio.ensure_future(coro()) ...Новая в версии 3.7.
Изменено в версии 3.8: Добавлен параметр
name.
Ожидание
-
coroutine asyncio.sleep(delay, result=None, *, loop=None) -
Ожидание delay секунд.
Если result задан, он возвращается вызывающей стороне по завершении корутины.
sleep()всегда приостанавливает текущую задачу, позволяя другим задачам выполняться.Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
Пример корутины, отображающей текущую дату каждую секунду в течение 5 секунд:
import asyncio import datetime async def display_date(): loop = asyncio.get_running_loop() end_time = loop.time() + 5.0 while True: print(datetime.datetime.now()) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(1) asyncio.run(display_date())
Выполнение задач одновременно
-
awaitable asyncio.gather(*aws, loop=None, return_exceptions=False) -
Запустить объекты-ожидания в последовательности aws параллельно.
Если какой-либо объект-ожидание в aws является корутиной, он автоматически планируется как задача.
Если все объекты-ожидания завершатся успешно, результатом является агрегированный список возвращаемых значений. Порядок значений результата соответствует порядку объектов-ожидания в aws.
Если return_exceptions равно
False(по умолчанию), первое поднятое исключение немедленно передаётся задаче, которая ожидает наgather(). Другие объекты-ожидания в последовательности aws не будут отменены и будут продолжать выполняться.Если return_exceptions равно
True, исключения обрабатываются так же, как успешные результаты, и агрегируются в списке результатов.Если
gather()отменено, все отправленные объекты-ожидания (которые ещё не завершились) также отменяются.Если какая-либо задача или будущее из последовательности aws отменяется, она обрабатывается так, как если бы она подняла
CancelledError– вызовgather()в этом случае не отменяется. Это предотвращает отмену одной отправленной задачи/будущего, чтобы привести к отмене других задач/будущих.Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
Пример:
import asyncio async def factorial(name, number): f = 1 for i in range(2, number + 1): print(f"Task {name}: Compute factorial({i})...") await asyncio.sleep(1) f *= i print(f"Task {name}: factorial({number}) = {f}") async def main(): # Schedule three calls *concurrently*: await asyncio.gather( factorial("A", 2), factorial("B", 3), factorial("C", 4), ) asyncio.run(main()) # Expected output: # # Task A: Compute factorial(2)... # Task B: Compute factorial(2)... # Task C: Compute factorial(2)... # Task A: factorial(2) = 2 # Task B: Compute factorial(3)... # Task C: Compute factorial(3)... # Task B: factorial(3) = 6 # Task C: Compute factorial(4)... # Task C: factorial(4) = 24Примечание
Если return_exceptions равно False, отмена gather() после того, как она была помечена как завершённая, не отменит какие-либо отправленные объекты-ожидания. Например, gather может быть помечена как завершённая после передачи исключения вызывающей стороне, поэтому вызов
gather.cancel()после перехвата исключения (поднятого одним из объектов-ожиданий) от gather не отменит никаких других объектов-ожиданий.Изменено в версии 3.7: Если сам gather отменяется, отмена передаётся независимо от return_exceptions.
Защита от отмены
-
awaitable asyncio.shield(aw, *, loop=None) -
Защищает объект-ожидание от
cancelled.Если aw является корутиной, она автоматически планируется как задача.
Выражение:
res = await shield(something())
эквивалентно:
res = await something()
кроме того, если корутина, содержащая его, отменяется, задача, выполняемая в
something()не отменяется. С точки зренияsomething(), отмена не произошла. Хотя её вызывающая сторона всё ещё отменена, поэтому выражение «await» всё равно поднимаетCancelledError.Если
something()отменяется другими способами (например, изнутри), это также отменитshield().Если необходимо полностью игнорировать отмену (не рекомендуется), функция
shield()должна быть объединена с блоком try/except, как показано ниже:try: res = await shield(something()) except CancelledError: res = NoneУстарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
Таймауты
-
coroutine asyncio.wait_for(aw, timeout, *, loop=None) -
Ожидать завершения aw выполнимого объекта с таймаутом.
Если aw является корутиной, она автоматически планируется как задача.
timeout может быть
Noneили числом с плавающей запятой или целым числом, указывающим количество секунд ожидания. Если timeoutNone, блокировать до завершения задачи.Если таймаут истекает, задача отменяется, и возбуждается
asyncio.TimeoutError.Чтобы избежать отмены задачи
cancellation, оберните её вshield().Функция будет ожидать, пока задача фактически не будет отменена, поэтому общее время ожидания может превысить timeout.
Если ожидание отменено, задача aw также отменяется.
Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
Пример:
async def eternity(): # Sleep for one hour await asyncio.sleep(3600) print('yay!') async def main(): # Wait for at most 1 second try: await asyncio.wait_for(eternity(), timeout=1.0) except asyncio.TimeoutError: print('timeout!') asyncio.run(main()) # Expected output: # # timeout!Изменено в версии 3.7: Когда aw отменяется из-за таймаута,
wait_forожидает, пока aw будет отменена. Ранее это вызывалоasyncio.TimeoutErrorнемедленно.
Ожидающие примитивы
-
coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED) -
Запустить выполнимые объекты в итерируемом объекте aws одновременно и заблокировать, пока не будет выполнено условие, заданное return_when.
Возвращает два набора задач/задач:
(done, pending).Использование:
done, pending = await asyncio.wait(aws)
timeout (число с плавающей запятой или целое число), если указан, может использоваться для управления максимальным временем ожидания в секундах перед возвратом.
Обратите внимание, что эта функция не вызывает
asyncio.TimeoutError. Задачи или объекты задач, которые не завершены при наступлении таймаута, просто возвращаются во второй набор.return_when указывает, когда эта функция должна вернуть значение. Она должна быть одним из следующих констант:
Константа
Описание
FIRST_COMPLETEDФункция вернётся, когда любая задача завершится или отменится.
FIRST_EXCEPTIONФункция вернётся, когда любая задача завершится, вызвав исключение. Если ни одна задача не вызывает исключение, то это эквивалентно
ALL_COMPLETED.ALL_COMPLETEDФункция вернётся, когда все задачи завершатся или отменятся.
В отличие от
wait_for(),wait()не отменяет задачи при наступлении таймаута.Устарело начиная с версии 3.8: Если любой выполнимый объект в aws является корутиной, он автоматически планируется как задача. Передача объектов корутин в
wait()напрямую устарела, так как приводит к непонятному поведению.Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
Примечание
wait()автоматически планирует корутины как задачи и впоследствии возвращает эти неявно созданные объекты задач в(done, pending)наборы. Поэтому следующий код не будет работать как ожидалось:async def foo(): return 42 coro = foo() done, pending = await asyncio.wait({coro}) if coro in done: # This branch will never be run!Вот как можно исправить приведенный выше фрагмент:
async def foo(): return 42 task = asyncio.create_task(foo()) done, pending = await asyncio.wait({task}) if task in done: # Everything will work as expected now.Устарело начиная с версии 3.8: Передача объектов корутин в
wait()напрямую устарела.
-
asyncio.as_completed(aws, *, loop=None, timeout=None) -
Запустить выполнимые объекты в итерируемом объекте aws одновременно. Возвращает итератор корутин. Каждая возвращённая корутина может быть ожидана для получения самого раннего следующего результата из итерируемого объекта оставшихся выполнимых объектов.
Вызывает
asyncio.TimeoutError, если таймаут наступит до того, как все задачи будут выполнены.Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
Пример:
for coro in as_completed(aws): earliest_result = await coro # ...
Расписание из других потоков
-
asyncio.run_coroutine_threadsafe(coro, loop) -
Отправить корутину в заданный цикл событий. Потокобезопасный.
Возвращает
concurrent.futures.Futureдля ожидания результата из другого потока ОС.Эта функция предназначена для вызова из другого потока ОС, отличного от того, в котором выполняется цикл событий. Пример:
# Create a coroutine coro = asyncio.sleep(1, result=3) # Submit the coroutine to a given loop future = asyncio.run_coroutine_threadsafe(coro, loop) # Wait for the result with an optional timeout argument assert future.result(timeout) == 3
Если в корутине возникнет исключение, возвращённое Future будет уведомлено. Его также можно использовать для отмены задачи в цикле событий:
try: result = future.result(timeout) except asyncio.TimeoutError: print('The coroutine took too long, cancelling the task...') future.cancel() except Exception as exc: print(f'The coroutine raised an exception: {exc!r}') else: print(f'The coroutine returned: {result!r}')См. раздел конкурентность и многопоточность документации.
В отличие от других функций asyncio, эта функция требует явного передачи аргумента loop.
Новая в версии 3.5.1.
Интроспекция
-
asyncio.current_task(loop=None) -
Возвращает текущую выполняемую
Taskэкземпляр илиNoneесли ни одна задача не выполняется.Если loop
Noneиспользуетсяget_running_loop()для получения текущего цикла.Новая в версии 3.7.
-
asyncio.all_tasks(loop=None) -
Возвращает множество не завершённых
Taskобъектов, запущенных циклом.Если loop
None, используетсяget_running_loop()для получения текущего цикла.Новая в версии 3.7.
Объект задачи
-
class asyncio.Task(coro, *, loop=None, name=None) -
Объект
Future-like, выполняющий Python корутину. Не потокобезопасен.Задачи используются для запуска корутин в циклах событий. Если корутина ожидает выполнения Future, задача приостанавливает выполнение корутины и ждёт завершения Future. Когда Future завершён, выполнение обернутой корутины возобновляется.
Циклы событий используют кооперативное планирование: цикл событий выполняет одну задачу за раз. Пока задача ожидает завершения Future, цикл событий выполняет другие задачи, обратные вызовы или выполняет операции ввода-вывода.
Используйте высокоуровневую функцию
asyncio.create_task()для создания задач или низкоуровневые функцииloop.create_task()илиensure_future(). Ручное создание задач не рекомендуется.Чтобы отменить выполняемую задачу, используйте метод
cancel(). Это вызовет исключениеCancelledErrorв обернутой корутине. Если корутина ожидает объекта Future во время отмены, объект Future будет отменён.Можно проверить, была ли задача отменена, с помощью
cancelled(). Метод возвращаетTrue, если обернутая корутина не подавила исключениеCancelledErrorи была действительно отменена.asyncio.Taskнаследует отFutureвсе свои API, кромеFuture.set_result()иFuture.set_exception().Задачи поддерживают модуль
contextvars. При создании задачи она копирует текущий контекст и затем выполняет свою корутину в скопированном контексте.Изменено в версии 3.7: Добавлена поддержка модуля
contextvars.Изменено в версии 3.8: Добавлен параметр
name.Устарело начиная с версии 3.8, будет удалено в версии 3.10: Параметр loop.
-
cancel() -
Запрос отмены задачи.
Это организует бросок исключения
CancelledErrorв обернутую корутину в следующем цикле цикла событий.Корутина затем имеет возможность очистить или даже отклонить запрос, подавив исключение с помощью блока
try… …except CancelledError…finally. Таким образом, в отличие отFuture.cancel(),Task.cancel()не гарантирует, что задача будет отменена, хотя полное подавление отмены не является распространённым и активно не рекомендуется.Следующий пример иллюстрирует, как корутины могут перехватывать запрос на отмену:
async def cancel_me(): print('cancel_me(): before sleep') try: # Wait for 1 hour await asyncio.sleep(3600) except asyncio.CancelledError: print('cancel_me(): cancel sleep') raise finally: print('cancel_me(): after sleep') async def main(): # Create a "cancel_me" Task task = asyncio.create_task(cancel_me()) # Wait for 1 second await asyncio.sleep(1) task.cancel() try: await task except asyncio.CancelledError: print("main(): cancel_me is cancelled now") asyncio.run(main()) # Expected output: # # cancel_me(): before sleep # cancel_me(): cancel sleep # cancel_me(): after sleep # main(): cancel_me is cancelled now
-
cancelled() -
Возвращает
True, если задача отменена.Задача считается отменённой, когда отмена была запрошена с помощью
cancel(), и обернутая корутина распространила исключениеCancelledError, брошенное в неё.
-
done() -
Возвращает
True, если задача завершена.Задача считается завершенной, когда обернутая корутина вернула значение, подняла исключение или задача была отменена.
-
-
result() -
Возвращает результат задачи.
Если задача завершена, возвращается результат обернутой корутины (или, если корутина подняла исключение, это исключение перевызывается).
Если задача была отменена, этот метод вызывает исключение
CancelledError.Если результат задачи еще недоступен, этот метод вызывает исключение
InvalidStateError.
-
exception() -
Возвращает исключение задачи.
Если обернутая корутина подняла исключение, возвращается это исключение. Если обернутая корутина завершилась без исключения, этот метод возвращает
None.Если задача была отменена, этот метод вызывает исключение
CancelledError.Если задача еще не завершена, этот метод вызывает исключение
InvalidStateError.
-
add_done_callback(callback, *, context=None) -
Добавляет обработчик, который будет выполнен, когда задача завершится.
Этот метод следует использовать только в коде низкого уровня, основанном на обратных вызовах.
См. документацию
Future.add_done_callback()для получения более подробной информации.
-
remove_done_callback(callback) -
Удаляет обработчик из списка обратных вызовов.
Этот метод следует использовать только в коде низкого уровня, основанном на обратных вызовах.
См. документацию
Future.remove_done_callback()для получения более подробной информации.
-
get_stack(*, limit=None) -
Возвращает список кадров стека для этой задачи.
Если обернутая корутина еще не завершена, возвращается стек, в котором она приостановлена. Если корутина завершилась успешно или была отменена, возвращается пустой список. Если корутина была прервана исключением, возвращается список кадров трассировки.
Кадры всегда упорядочены от старых к новым.
Для приостановленной корутины возвращается только один кадр стека.
Необязательный аргумент limit устанавливает максимальное количество возвращаемых кадров; по умолчанию возвращаются все доступные кадры. Порядок возвращаемого списка отличается в зависимости от того, возвращается ли стек или трассировка: возвращаются самые новые кадры стека, но самые старые кадры трассировки. (Это соответствует поведению модуля traceback).
-
print_stack(*, limit=None, file=None) -
Выводит стек или трассировку для этой задачи.
Это производит вывод, аналогичный выводу модуля traceback для кадров, полученных с помощью
get_stack().Аргумент limit передается в
get_stack()напрямую.Аргумент file — поток ввода-вывода, в который записывается вывод; по умолчанию вывод записывается в
sys.stderr.
-
get_coro() -
Возвращает объект корутины, обернутый в
Task.Новая в версии 3.8.
-
get_name() -
Возвращает имя задачи.
Если задаче явно не было присвоено имя, реализация задачи asyncio по умолчанию генерирует имя по умолчанию во время создания.
Новая в версии 3.8.
-
set_name(value) -
Устанавливает имя задачи.
Аргумент value может быть любым объектом, который затем преобразуется в строку.
В реализации задачи по умолчанию имя будет видно в выводе
repr()объекта задачи.Новая в версии 3.8.
-
-
classmethod all_tasks(loop=None) -
Возвращает множество всех задач для цикла событий.
По умолчанию возвращаются все задачи для текущего цикла событий. Если loop задан, функция
get_event_loop()используется для получения текущего цикла.Устарело начиная с версии 3.7, будет удалено в версии 3.9: Не вызывайте этот метод как метод задачи. Используйте функцию
asyncio.all_tasks()вместо этого.
-
classmethod current_task(loop=None) -
Возвращает текущую выполняющуюся задачу или
None.Если loop задан, функция
get_event_loop()используется для получения текущего цикла.Устарело начиная с версии 3.7, будет удалено в версии 3.9: Не вызывайте этот метод как метод задачи. Используйте функцию
asyncio.current_task()вместо этого.
-
Генераторные корутины
Примечание
Поддержка генераторных корутин устарела и будет удалена в Python 3.10.
Генераторные корутины предшествуют синтаксису async/await. Это генераторы Python, которые используют yield from выражения для ожидания на Futures и других корутин.
Генераторные корутины должны быть декорированы с помощью @asyncio.coroutine, хотя это не обязательно.
-
@asyncio.coroutine -
Декоратор для маркировки генераторных корутин.
Этот декоратор позволяет корутинам на основе генераторов из прошлого быть совместимыми с кодом async/await:
@asyncio.coroutine def old_style_coroutine(): yield from asyncio.sleep(1) async def main(): await old_style_coroutine()Этот декоратор не следует использовать для корутин
async def.Устарело начиная с версии 3.8, будет удалено в версии 3.10: Используйте
async defвместо этого.
-
asyncio.iscoroutine(obj) -
Возвращает
Trueесли obj является объектом корутины.Этот метод отличается от
inspect.iscoroutine(), поскольку возвращаетTrueдля генераторных корутин.
-
asyncio.iscoroutinefunction(func) -
Возвращает
Trueесли func является функцией корутины.Этот метод отличается от
inspect.iscoroutinefunction(), поскольку возвращаетTrueдля функций генераторных корутин, декорированных@coroutine.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/library/asyncio-task.html