Какой механизм в asyncio сообщает циклу событий о завершении корутины?

«Какой механизм в asyncio сообщает циклу событий о завершении корутины?» — вопрос из категории Асинхронность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Цикл событий в asyncio узнает о завершении корутины через объекты Task и Future.

Ключевые компоненты механизма:

  1. Future: Это объект-заместитель, который представляет конечный результат асинхронной операции. Он может находиться в состоянии ожидания (pending) или завершения (finished) и хранить либо результат, либо исключение.

  2. Task: Это подкласс Future, специально предназначенный для выполнения корутин в цикле событий. Когда вы создаете задачу с помощью asyncio.create_task(my_coro()), вы, по сути, говорите циклу событий: "Вот корутина, начни ее выполнять и дай мне Task, чтобы я мог отслеживать ее состояние".

Как это работает:

  • Когда корутина завершается (через return или raise Exception), обернувший ее Task перехватывает результат или исключение.
  • Task устанавливает этот результат в свой внутренний Future и меняет свое состояние на "завершено".
  • Task уведомляет цикл событий о своем завершении, вызывая специальные коллбэки. Если какая-то другая корутина ожидала эту задачу через await, ее выполнение возобновляется.

Пример:

import asyncio

async def fetch_data():
    print("Начинаю загрузку данных...")
    await asyncio.sleep(2)  # Имитация долгой операции
    print("Данные загружены")
    return {"data": 123}

async def main():
    print("Создаю задачу для fetch_data")
    # 1. Корутина оборачивается в Task и планируется к выполнению
    task = asyncio.create_task(fetch_data())

    # 2. main() приостанавливается и ждет, пока task не завершится
    #    К task привязывается коллбэк, который возобновит main()
    result = await task

    # 3. После завершения task, main() возобновляется, и результат доступен
    print(f"Задача завершена: {task.done()}")
    print(f"Полученный результат: {result}")

asyncio.run(main())

Таким образом, цикл событий не "опрашивает" задачи, а реагирует на события их завершения через систему коллбэков, привязанных к объектам Future/Task.