Spec-Zone.ru › Python 3.7

Корутины и задачи

В этом разделе описаны высокоуровневые API asyncio для работы с корутинами и задачами.

  • Корутины
  • Объекты-ожидатели
  • Запуск программы 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() для одновременного запуска корутин как asyncio Tasks.

    Давайте изменим вышеприведенный пример и запустим две 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
    

Объекты-ожидатели

Мы говорим, что объект является объектом-ожидателем, если он может быть использован в выражении await. Многие API asyncio предназначены для работы с объектами-ожидателями.

Существует три основных типа объектов-ожидателей: корутины, задачи и будущие.

Корутины

Python корутины являются объектами-ожидателями и поэтому могут быть ожидаемыми другими корутинами:

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 — это специальный низкоуровневый объект-ожидатель, который представляет собой конечный результат асинхронной операции.

Когда объект 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())

New in version 3.7: Важно: эта функция была добавлена в asyncio в Python 3.7 на временной основе.

Создание задач

asyncio.create_task(coro)

Оборачивает корутину coro в Task и планирует её выполнение. Возвращает объект Task.

Задача выполняется в цикле, возвращаемом 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())
...

New in version 3.7.

Ожидание

coroutine asyncio.sleep(delay, result=None, *, loop=None)

Ожидать delay секунд.

Если result предоставлен, он возвращается вызывающей стороне, когда корутина завершается.

sleep() всегда приостанавливает текущую задачу, позволяя другим задачам выполняться.

Аргумент loop устарел и будет удален в Python 3.10.

Пример корутины, отображающей текущую дату каждую секунду в течение 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() отменён, все переданные объекты-ожидатели (которые ещё не завершены) также отменяются.

Если какая-либо задача или Future из последовательности aws отменена, она обрабатывается так, как если бы она подняла CancelledError — вызов gather() в этом случае не отменяется. Это предотвращает отмену одной переданной задачи/Future, чтобы вызвать отмену других задач/Future.

Пример:

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

Изменено в версии 3.7: Если сам gather отменён, отмена распространяется независимо от return_exceptions.

Защита от отмены

awaitable asyncio.shield(aw, *, loop=None)

Защитить объект-awaitable от 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

Таймауты

coroutine asyncio.wait_for(aw, timeout, *, loop=None)

Подождать завершения aw awaitable с таймаутом.

Если aw — это корутина, она автоматически планируется как задача.

timeout может быть None или числом с плавающей точкой или целым числом секунд для ожидания. Если timeout — None, ожидать завершения фьючера.

Если таймаут истечёт, задача отменяется, и возникает asyncio.TimeoutError.

Чтобы избежать отмены задачи cancellation, оберните её в shield().

Функция будет ожидать, пока фьючер не будет фактически отменён, поэтому общее время ожидания может превысить timeout.

Если ожидание отменено, фьючер aw также отменяется.

Аргумент loop устарел и запланирован к удалению в Python 3.10.

Пример:

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)

Выполнить объекты-awaitable в наборе aws одновременно и блокировать до тех пор, пока не будет выполнено условие, указанное в return_when.

Если какой-либо awaitable в aws — это корутина, она автоматически планируется как задача. Передача объектов корутин в wait() напрямую устарела, так как это приводит к непонятному поведению.

Возвращает два набора задач/фьючеров: (done, pending).

Использование:

done, pending = await asyncio.wait(aws)

Аргумент loop устарел и запланирован к удалению в Python 3.10.

timeout (число с плавающей точкой или целое число), если указано, может использоваться для управления максимальным временем ожидания в секундах перед возвращением.

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

return_when указывает, когда эта функция должна вернуть результат. Он должен быть одним из следующих констант:

Константа

Описание

FIRST_COMPLETED

Функция вернёт результат, когда любой фьючер завершится или отменится.

FIRST_EXCEPTION

Функция вернёт результат, когда любой фьючер завершится с исключением. Если ни один фьючер не вызывает исключение, это эквивалентно ALL_COMPLETED.

ALL_COMPLETED

Функция вернёт результат, когда все фьючеры завершатся или отменятся.

В отличие от wait_for(), wait() не отменяет фьючеры при возникновении таймаута.

Примечание

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.

Передача объектов корутин в wait() напрямую устарела.

asyncio.as_completed(aws, *, loop=None, timeout=None)

Выполнить объекты-awaitable в наборе aws одновременно. Вернуть итератор Future объектов. Каждый возвращённый объект Future представляет собой самый ранний результат из набора оставшихся awaitable объектов.

Вызывает asyncio.TimeoutError, если таймаут истекает до завершения всех Future.

Пример:

for f in as_completed(aws):
    earliest_result = await f
    # ...

Планирование из других потоков

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)

Объект Future-like, выполняющий Python-корутину. Не потокобезопасен.

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

Циклы событий используют кооперативное планирование: цикл событий выполняет одну задачу за раз. Пока задача ожидает завершения Future, цикл событий выполняет другие задачи, обратные вызовы или выполняет операции ввода-вывода.

Используйте функцию высокого уровня asyncio.create_task() для создания задач или низкоуровневые функции loop.create_task() или ensure_future(). Ручное создание задач не рекомендуется.

Для отмены выполняемой задачи используйте метод cancel(). Его вызов заставит задачу сгенерировать исключение CancelledError в обернутой корутине. Если корутина ожидает объект Future во время отмены, объект Future будет отменён.

cancelled() может быть использован для проверки, была ли задача отменена. Метод возвращает True если обернутая корутина не подавила исключение CancelledError и фактически была отменена.

asyncio.Task наследует все свои API от Future, за исключением Future.set_result() и Future.set_exception().

Задачи поддерживают модуль contextvars. При создании задачи она копирует текущий контекст и затем выполняет свою корутину в скопированном контексте.

Изменено в версии 3.7: Добавлена поддержка модуля contextvars.

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.

classmethod all_tasks(loop=None)

Возвращает множество всех задач для цикла событий.

По умолчанию возвращаются все задачи для текущего цикла событий. Если loop None, используется функция get_event_loop() для получения текущего цикла.

Этот метод устарел и будет удалён в Python 3.9. Используйте функцию asyncio.all_tasks() вместо неё.

classmethod current_task(loop=None)

Возвращает текущую выполняющуюся задачу или None.

Если loop None, используется функция get_event_loop() для получения текущего цикла.

Этот метод устарел и будет удалён в Python 3.9. Используйте функцию asyncio.current_task() вместо неё.

Корутины на основе генераторов

Примечание

Поддержка корутин на основе генераторов устарела и запланирована на удаление в Python 3.10.

Корутины на основе генераторов появились до синтаксиса async/await. Это Python-генераторы, которые используют yield from выражения для ожидания Future и других корутин.

Корутины на основе генераторов должны быть декорированы с помощью @asyncio.coroutine, хотя это не обязательно.

@asyncio.coroutine

Декоратор для обозначения генераторных корутин.

Этот декоратор позволяет устаревшим генераторным корутинам быть совместимыми с кодом async/await:

@asyncio.coroutine
def old_style_coroutine():
    yield from asyncio.sleep(1)

async def main():
    await old_style_coroutine()

Этот декоратор устарел и запланирован к удалению в Python 3.10.

Этот декоратор не следует использовать для async def корутин.

asyncio.iscoroutine(obj)

Возвращает True, если obj является объектом корутины.

Этот метод отличается от inspect.iscoroutine(), потому что возвращает True для генераторных корутин.

asyncio.iscoroutinefunction(func)

Возвращает True, если func является функцией корутины.

Этот метод отличается от inspect.iscoroutinefunction(), потому что возвращает True для генераторных функций-корутин, декорированных с помощью @coroutine.

© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/asyncio-task.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API