Spec-Zone.ru › Python 3.12

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

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

  • Сорутины
  • Awaitables
  • Создание задач
  • Отмена задач
  • Группы задач
  • Ожидание
  • Выполнение задач одновременно
  • Жадный фабрикант задач
  • Защита от отмены
  • Таймауты
  • Примитивы ожидания
  • Выполнение в потоках
  • Планирование из других потоков
  • Интроспекция
  • Объект задачи

Сорутины

Исходный код: Lib/asyncio/coroutines.py

Сорутины, объявленные с помощью синтаксиса async/await, — это предпочтительный способ написания приложений asyncio. Например, следующий фрагмент кода выводит «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
    
  • Класс asyncio.TaskGroup предоставляет более современную альтернативу create_task(). Используя этот API, последний пример становится:

    async def main():
        async with asyncio.TaskGroup() as tg:
            task1 = tg.create_task(
                say_after(1, 'hello'))
    
            task2 = tg.create_task(
                say_after(2, 'world'))
    
            print(f"started at {time.strftime('%X')}")
    
        # The await is implicit when the context manager exits.
    
        print(f"finished at {time.strftime('%X')}")
    

    Временные характеристики и вывод должны быть такими же, как и в предыдущей версии.

    Добавлена в версии 3.11: asyncio.TaskGroup.

Awaitables

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

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

Сорутины

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

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.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().

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

Исходный код: Lib/asyncio/tasks.py

asyncio.create_task(coro, *, name=None, context=None)

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

Если name не None, он устанавливается как имя задачи с помощью Task.set_name().

Необязательный ключевой аргумент context позволяет указать пользовательский contextvars.Context для выполнения coro. Копия текущего контекста создаётся, если context не указан.

Задача выполняется в цикле, возвращённом get_running_loop(), RuntimeError генерируется, если в текущем потоке нет работающего цикла.

Примечание

asyncio.TaskGroup.create_task() — это новая альтернатива, использующая структурную конкуретность; она позволяет ожидать группу связанных задач с сильными гарантиями безопасности.

Важно

Сохраните ссылку на результат этой функции, чтобы избежать исчезновения задачи во время выполнения. Цикл событий хранит только слабые ссылки на задачи. Задача, на которую нет других ссылок, может быть удалена сборщиком мусора в любой момент, даже до завершения. Для надёжных «забытых» фоновых задач соберите их в коллекции:

background_tasks = set()

for i in range(10):
    task = asyncio.create_task(some_coro(param=i))

    # Add task to the set. This creates a strong reference.
    background_tasks.add(task)

    # To prevent keeping references to finished tasks forever,
    # make each task remove its own reference from the set after
    # completion:
    task.add_done_callback(background_tasks.discard)

Добавлена в версии 3.7.

Изменена в версии 3.8: Добавлен параметр name.

Изменена в версии 3.11: Добавлен параметр context.

Отмена задач

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

Рекомендуется, чтобы сорутины использовали блоки try/finally для надёжной реализации логики очистки. В случае, если asyncio.CancelledError явно перехватывается, его следует распространять после завершения очистки. asyncio.CancelledError напрямую наследуется от BaseException, поэтому большинство кода не должны быть осведомлены об этом.

Компоненты asyncio, поддерживающие структурированную конкуретность, такие как asyncio.TaskGroup и asyncio.timeout(), реализованы с использованием внутренней отмены и могут работать некорректно, если сорутина подавляет asyncio.CancelledError. Аналогично, пользовательский код обычно не должен вызывать uncancel. Однако, в случаях, когда подавление asyncio.CancelledError действительно необходимо, необходимо также вызвать uncancel() для полного удаления состояния отмены.

Группы задач

Группы задач объединяют API создания задач с удобным и надежным способом ожидания завершения всех задач в группе.

class asyncio.TaskGroup

Управляемый контекст асинхронного менеджера контекста содержащий группу задач. Задачи могут быть добавлены в группу с помощью create_task(). Все задачи ожидают завершения при выходе из менеджера контекста.

Добавлен в версии 3.11.

create_task(coro, *, name=None, context=None)

Создает задачу в этой группе задач. Подпись соответствует подписи asyncio.create_task().

Пример:

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(some_coro(...))
        task2 = tg.create_task(another_coro(...))
    print(f"Both tasks have completed now: {task1.result()}, {task2.result()}")

Инструкция async with будет ожидать завершения всех задач в группе. В процессе ожидания новые задачи могут быть добавлены в группу (например, передав tg в одну из корутин и вызвав tg.create_task() в этой корутине). После завершения последней задачи и выхода из блока async with, новые задачи не могут быть добавлены в группу.

В первый раз, когда любая из задач в группе завершится с исключением, отличным от asyncio.CancelledError, оставшиеся задачи в группе отменяются. После этого больше задач в группу добавить нельзя. В этот момент, если тело инструкции async with всё ещё активно (т.е. __aexit__() ещё не было вызвано), сама задача, содержащая инструкцию async with также отменяется. Результатом будет asyncio.CancelledError, который прервёт await, но он не будет распространяться за пределы содержащей async with инструкции.

После завершения всех задач, если какие-либо задачи завершились с исключением, отличным от asyncio.CancelledError, эти исключения объединяются в ExceptionGroup или BaseExceptionGroup (в зависимости от ситуации; см. их документацию), которое затем и генерируется.

Два базовых исключения обрабатываются особым образом: если какая-либо задача завершается с KeyboardInterrupt или SystemExit, группа задач всё равно отменяет оставшиеся задачи и ожидает их завершения, но затем исходное KeyboardInterrupt или SystemExit переиздаётся вместо ExceptionGroup или BaseExceptionGroup.

Если тело инструкции async with завершается с исключением (т.е. вызывается __aexit__() с установленным исключением), это обрабатывается так же, как если бы одна из задач завершилась с исключением: оставшиеся задачи отменяются, и затем ожидают завершения. Исключение, не являющееся исключением отмены, объединяется в группу исключений и генерируется. Исключение, переданное в __aexit__(), если оно не является asyncio.CancelledError, также включается в группу исключений. Для KeyboardInterrupt и SystemExit применяется тот же специальный случай, что и в предыдущем абзаце.

Ожидание

coroutine asyncio.sleep(delay, result=None)

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

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

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

Установка задержки в 0 предоставляет оптимизированный путь, позволяющий другим задачам выполняться. Это может быть использовано функциями с длительным выполнением, чтобы избежать блокировки цикла событий на всё время выполнения функции.

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

Изменено в версии 3.10: Убран параметр loop.

Запуск задач параллельно

awaitable asyncio.gather(*aws, return_exceptions=False)

Запуск объектов-ожиданий в последовательности aws параллельно.

Если любой объект-ожидание в aws является корутиной, он автоматически планируется как задача.

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

Если return_exceptions False (по умолчанию), первое сгенерированное исключение немедленно передаётся задаче, которая ожидает gather(). Другие объекты-ожидания в последовательности aws не будут отменены и будут продолжать выполняться.

Если return_exceptions True, исключения обрабатываются так же, как успешные результаты, и агрегируются в списке результатов.

Если gather() отменён, все отправленные объекты-ожидания (которые ещё не завершены) также отменяются.

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

Примечание

Альтернативным способом создания и запуска задач параллельно и ожидания их завершения является asyncio.TaskGroup. TaskGroup обеспечивает более сильные гарантии безопасности по сравнению с gather для планирования вложенных подзадач: если задача (или подзадача, задача, запланированная другой задачей) генерирует исключение, TaskGroup, в отличие от gather, отменит оставшиеся запланированные задачи.

Пример:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({number}), currently i={i}...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")
    return f

async def main():
    # Schedule three calls *concurrently*:
    L = await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )
    print(L)

asyncio.run(main())

# Expected output:
#
#     Task A: Compute factorial(2), currently i=2...
#     Task B: Compute factorial(3), currently i=2...
#     Task C: Compute factorial(4), currently i=2...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3), currently i=3...
#     Task C: Compute factorial(4), currently i=3...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4), currently i=4...
#     Task C: factorial(4) = 24
#     [2, 6, 24]

Примечание

Если return_exceptions ложно, отмена gather() после того, как он был помечен как выполненный, не отменяет никаких отправленных объектов-ожиданий. Например, gather может быть помечен как выполненный после распространения исключения вызывающей стороне, поэтому вызов gather.cancel() после перехвата исключения (сгенерированного одним из объектов-ожиданий) от gather не отменит другие объекты-ожидания.

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

Изменено в версии 3.10: Убран параметр loop.

Устарело начиная с версии 3.10: Если не указаны позиционные аргументы или не все позиционные аргументы являются объектами типа Future, и нет активного цикла событий, выводится предупреждение об устаревании.

Фабрика задач с немедленным выполнением

asyncio.eager_task_factory(loop, coro, *, name=None, context=None)

Фабрика задач для немедленного выполнения.

При использовании этой фабрики (через loop.set_task_factory(asyncio.eager_task_factory)), корутины начинают выполняться синхронно во время создания Task. Задачи планируются в цикле событий только в случае блокировки. Это может улучшить производительность, так как исключается накладные расходы на планирование цикла событий для корутин, которые завершаются синхронно.

Частый пример, где это полезно, — корутины, использующие кэширование или запоминание, чтобы избежать реального ввода-вывода, когда это возможно.

Примечание

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

Добавлена в версии 3.12.

asyncio.create_eager_task_factory(custom_task_constructor)

Создает фабрику задач с немедленным выполнением, аналогично eager_task_factory(), используя предоставленный custom_task_constructor при создании новой задачи вместо стандартного Task.

custom_task_constructor должен быть вызываемым объектом со сигнатурой, соответствующей сигнатуре Task.__init__. Вызываемый объект должен возвращать объект, совместимый с asyncio.Task.

Эта функция возвращает вызываемый объект, предназначенный для использования в качестве фабрики задач цикла событий через loop.set_task_factory(factory).

Добавлена в версии 3.12.

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

awaitable asyncio.shield(aw)

Защищает объект-выражение-ожидания от отмены cancelled.

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

Выражение:

task = asyncio.create_task(something())
res = await shield(task)

эквивалентно:

res = await something()

за исключением того, что если корутина, содержащая его, отменяется, задача, выполняющаяся в something() не отменяется. С точки зрения something(), отмена не произошла. Хотя вызывающий её код всё равно отменён, поэтому выражение «await» по-прежнему вызывает CancelledError.

Если something() отменяется другими способами (т.е. изнутри), это также отменяет shield().

Если нужно полностью игнорировать отмену (не рекомендуется), функция shield() должна использоваться совместно с блоком try/except, как показано ниже:

task = asyncio.create_task(something())
try:
    res = await shield(task)
except CancelledError:
    res = None

Важно

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

Изменено в версии 3.10: Параметр loop удален.

Устарело начиная с версии 3.10: Выводится предупреждение об устаревании, если aw не является объектом типа Future и нет работающего цикла событий.

Таймауты

asyncio.timeout(delay)

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

delay может быть None, или числом/вещественным числом секунд для ожидания. Если delay None, ограничение по времени не будет применено; это может быть полезно, если задержка неизвестна при создании контекстного менеджера.

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

Пример:

async def main():
    async with asyncio.timeout(10):
        await long_running_task()

Если long_running_task займёт более 10 секунд, контекстный менеджер отменит текущую задачу и обработает возникшее asyncio.CancelledError внутри, преобразуя его в TimeoutError, которое можно перехватить и обработать.

Примечание

Контекстный менеджер asyncio.timeout() преобразует asyncio.CancelledError в TimeoutError, что означает, что TimeoutError может быть перехвачен вне контекстного менеджера.

Пример перехвата TimeoutError:

async def main():
    try:
        async with asyncio.timeout(10):
            await long_running_task()
    except TimeoutError:
        print("The long operation timed out, but we've handled it.")

    print("This statement will run regardless.")

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

class asyncio.Timeout(when)

Асинхронный контекстный менеджер для отмены просроченных корутин.

when должно быть абсолютным временем истечения срока действия контекста, измеренным по часам цикла событий:

  • Если when равно None, таймаут никогда не сработает.
  • Если when < loop.time(), таймаут сработает на следующей итерации цикла событий.
when() → float | None

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

reschedule(when: float | None)

Перепланировать таймаут.

expired() → bool

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

Пример:

async def main():
    try:
        # We do not know the timeout when starting, so we pass ``None``.
        async with asyncio.timeout(None) as cm:
            # We know the timeout now, so we reschedule it.
            new_deadline = get_running_loop().time() + 10
            cm.reschedule(new_deadline)

            await long_running_task()
    except TimeoutError:
        pass

    if cm.expired():
        print("Looks like we haven't finished on time.")

Контекстные менеджеры таймаута могут быть вложены безопасно.

Добавлена в версии 3.11.

asyncio.timeout_at(when)

Аналогично asyncio.timeout(), за исключением того, что when — это абсолютное время остановки ожидания или None.

Пример:

async def main():
    loop = get_running_loop()
    deadline = loop.time() + 20
    try:
        async with asyncio.timeout_at(deadline):
            await long_running_task()
    except TimeoutError:
        print("The long operation timed out, but we've handled it.")

    print("This statement will run regardless.")

Добавлена в версии 3.11.

coroutine asyncio.wait_for(aw, timeout)

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

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

timeout может быть None или числом (float или int) секунд для ожидания. Если timeout None, ждать завершения будущего.

В случае таймаута, задача отменяется и генерируется TimeoutError.

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

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

Если ожидание отменено, будущее aw также отменяется.

Пример:

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 TimeoutError:
        print('timeout!')

asyncio.run(main())

# Expected output:
#
#     timeout!

Изменено в версии 3.7: Когда aw отменяется из-за таймаута, wait_for ждёт отмены aw. Раньше оно сразу генерировало TimeoutError.

Изменено в версии 3.10: Параметр loop удалён.

Изменено в версии 3.11: Возвращает TimeoutError вместо asyncio.TimeoutError.

Операции ожидания

coroutine asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)

Выполните экземпляры Future и Task в итерируемом объекте aws одновременно и заблокируйтесь до тех пор, пока не будет выполнено условие, заданное параметром return_when.

Итерируемый объект aws не должен быть пустым.

Возвращает два набора задач/будущих: (done, pending).

Пример использования:

done, pending = await asyncio.wait(aws)

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

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

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

Константа

Описание

asyncio.FIRST_COMPLETED

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

asyncio.FIRST_EXCEPTION

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

asyncio.ALL_COMPLETED

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

В отличие от wait_for(), wait() не отменяет будущие задачи при наступлении таймаута.

Изменено в версии 3.10: Устранён параметр loop.

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

Изменено в версии 3.12: Добавлена поддержка генераторов, возвращающих задачи.

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

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

Вызывает TimeoutError, если таймаут наступает до того, как все будущие задачи завершатся.

Пример:

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

Изменено в версии 3.10: Устранён параметр loop.

Устарело начиная с версии 3.10: Выводится предупреждение об устаревании, если все awaitable объекты в итерируемом объекте aws не являются объектами типа Future, и нет работающего цикла событий.

Изменено в версии 3.12: Добавлена поддержка генераторов, возвращающих задачи.

Запуск в потоках

coroutine asyncio.to_thread(func, /, *args, **kwargs)

Асинхронно выполняет функцию func в отдельном потоке.

Любые *args и **kwargs, переданные в эту функцию, напрямую передаются в func. Также, текущий contextvars.Context распространяется, позволяя обращаться к переменным контекста из потока цикла событий в отдельном потоке.

Возвращает корутину, которую можно ожидать, чтобы получить конечный результат func.

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

def blocking_io():
    print(f"start blocking_io at {time.strftime('%X')}")
    # Note that time.sleep() can be replaced with any blocking
    # IO-bound operation, such as file operations.
    time.sleep(1)
    print(f"blocking_io complete at {time.strftime('%X')}")

async def main():
    print(f"started main at {time.strftime('%X')}")

    await asyncio.gather(
        asyncio.to_thread(blocking_io),
        asyncio.sleep(1))

    print(f"finished main at {time.strftime('%X')}")


asyncio.run(main())

# Expected output:
#
# started main at 19:50:53
# start blocking_io at 19:50:53
# blocking_io complete at 19:50:54
# finished main at 19:50:54

Прямое вызов blocking_io() в любой корутине заблокирует цикл событий на всё время её выполнения, что приведёт к дополнительному 1 секунде времени выполнения. Вместо этого, используя asyncio.to_thread(), мы можем выполнить его в отдельном потоке, не блокируя цикл событий.

Примечание

Из-за GIL, asyncio.to_thread() обычно можно использовать только для того, чтобы сделать функции, связанные с вводом-выводом, неблокирующими. Однако, для модулей расширения, которые освобождают GIL или альтернативных реализаций Python, которые его не имеют, asyncio.to_thread() также можно использовать для функций, связанных с вычислениями.

Добавлена в версии 3.9.

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

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

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

try:
    result = future.result(timeout)
except 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 не указан, используется get_running_loop() для получения текущего цикла.

Добавлена в версии 3.7.

asyncio.all_tasks(loop=None)

Возвращает множество ещё не завершённых объектов Task, запущенных циклом.

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

Добавлена в версии 3.7.

asyncio.iscoroutine(obj)

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

Добавлена в версии 3.4.

END_OF_DOCUMENT_MARKER

Объект задачи

class asyncio.Task(coro, *, loop=None, name=None, context=None, eager_start=False)

Объект 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().

Необязательный ключевой аргумент context позволяет указать пользовательский contextvars.Context для выполнения coro. Если context не указан, задача копирует текущий контекст и позже выполняет свою корутину в скопированном контексте.

Необязательный ключевой аргумент eager_start позволяет начать выполнение asyncio.Task при создании задачи. Если установлено True, и цикл событий запущен, задача начнёт немедленно выполнять корутину, до первого момента блокировки корутины. Если корутина возвращается или вызывает исключение без блокировки, задача завершится сразу и пропустит планирование в цикле событий.

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

Изменено в версии 3.8: Добавлен параметр name.

Устарело начиная с версии 3.10: Выдаётся предупреждение об устаревании, если loop не указан и нет работающего цикла событий.

Изменено в версии 3.11: Добавлен параметр context.

Изменено в версии 3.12: Добавлен параметр eager_start.

done()

Возвращает True, если задача завершена.

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

result()

Возвращает результат выполнения задачи.

Если задача завершена, возвращается результат выполнения обернутой корутины (или, если корутина вызвала исключение, то это исключение перевыдаётся).

Если задача была отменена, этот метод вызывает исключение CancelledError.

Если результат задачи ещё недоступен, этот метод вызывает исключение InvalidStateError.

exception()

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

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

Если задача была отменена, этот метод вызывает исключение CancelledError.

Если задача ещё не завершена, этот метод вызывает исключение InvalidStateError.

add_done_callback(callback, *, context=None)

Добавляет обратный вызов, который будет выполнен, когда задача завершится.

Этот метод следует использовать только в низкоуровневом коде, основанном на обратных вызовах.

См. документацию Future.add_done_callback() для получения более подробной информации.

remove_done_callback(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.stdout.

get_coro()

Возвращает объект корутины, обернутый в Task.

Примечание

Для задач, которые уже завершились быстро, это вернёт None. См. Eager Task Factory.

Добавлена в версии 3.8.

Изменено в версии 3.12: Новое добавление быстрого выполнения задачи означает, что результат может быть None.

get_context()

Возвращает объект contextvars.Context, связанный с задачей.

Добавлена в версии 3.12.

get_name()

Возвращает имя задачи.

Если задаче явно не было присвоено имя, реализация asyncio Task по умолчанию генерирует имя по умолчанию во время создания.

Добавлена в версии 3.8.

set_name(value)

Устанавливает имя задачи.

Аргумент value может быть любым объектом, который затем преобразуется в строку.

В реализации задачи по умолчанию имя будет видно в выводе repr() объекта задачи.

Добавлена в версии 3.8.

END_OF_DOCUMENT_MARKER
cancel(msg=None)

Запросить отмену задачи.

Это организует бросок исключения CancelledError в оборачиваемую корутину в следующем цикле событий.

Корутина получает возможность выполнить очистку или даже отклонить запрос, подавив исключение с помощью блока try … … except CancelledError … finally. Поэтому, в отличие от Future.cancel(), Task.cancel() не гарантирует отмену задачи, хотя полное подавление отмены не является распространенной практикой и активно не рекомендуется. Если корутина все же решит подавить отмену, ей необходимо вызвать Task.uncancel() в дополнение к перехвату исключения.

Изменено в версии 3.9: Добавлен параметр msg.

Изменено в версии 3.11: Параметр msg передается от отменённой задачи к её ожидателю.

Следующий пример иллюстрирует, как корутины могут перехватывать запрос на отмену:

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, сгенерированное в ней.

uncancel()

Уменьшает счётчик запросов на отмену для этой задачи.

Возвращает оставшееся количество запросов на отмену.

Обратите внимание, что после завершения выполнения отменённой задачи дальнейшие вызовы uncancel() неэффективны.

Добавлена в версии 3.11.

Этот метод используется внутренними механизмами asyncio и не должен использоваться кодом конечного пользователя. В частности, если задача успешно отменена, это позволяет элементам структурированной конкурентности, таким как Группы задач и asyncio.timeout() , продолжить работу, изолировав отмену для соответствующего структурированного блока. Например:

async def make_request_with_timeout():
    try:
        async with asyncio.timeout(1):
            # Structured block affected by the timeout:
            await make_request()
            await make_another_request()
    except TimeoutError:
        log("There was a timeout")
    # Outer code not affected by the timeout:
    await unrelated_code()

Хотя блок с make_request() и make_another_request() может быть отменён из-за таймаута, unrelated_code() должен продолжать работу даже в случае таймаута. Это реализовано с помощью uncancel(). Блоки-менеджеры TaskGroup используют uncancel() аналогичным образом.

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

cancelling()

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

Обратите внимание, что если это число больше нуля, но задача всё ещё выполняется, cancelled() всё равно вернёт False. Это связано с тем, что это число может быть уменьшено вызовом uncancel(), что может привести к тому, что задача не будет отменена, если запросы на отмену станут равны нулю.

Этот метод используется внутренними механизмами asyncio и не должен использоваться кодом конечного пользователя. См. uncancel() для получения дополнительной информации.

Добавлена в версии 3.11.

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

Spec-Zone.ru

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