Ответ
Цикл событий в asyncio узнает о завершении корутины через объекты Task и Future.
Ключевые компоненты механизма:
-
Future: Это объект-заместитель, который представляет конечный результат асинхронной операции. Он может находиться в состоянии ожидания (pending) или завершения (finished) и хранить либо результат, либо исключение. -
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.
Ответ 18+ 🔞
А, ну вот, слушай, сейчас я тебе на пальцах объясню, как эта асинхронная магия работает, а то у тебя, я смотрю, глаза уже квадратные от этих Future и Task.
Представь себе, что цикл событий — это такой заведующий складом, который сидит на троне и пьёт чай. А корутины — это грузчики, которые ему подчиняются.
Так вот, этот Future — это, блядь, накладная. Просто бумажка, на которой написано: «Результат работы грузчика Иванова». Пока грузчик Иванов таскает коробки, накладная пустая, состояние — «в процессе». Как только он закончил — он пишет на ней «всё, сделал, вот 10 коробок» или «ёбта, ногу сломал» и кладёт её на стол заведующему.
А Task — это сам грузчик Иванов, но с приколоченной к спине этой самой накладной! То есть, когда ты пишешь asyncio.create_task(my_coro()), ты не просто говоришь «Иванов, иди работать». Ты говоришь: «Иванов, иди работать, и вот тебе накладная, которую ты сам и заполнишь, когда закончишь». И заведующий (цикл событий) сразу видит: ага, вот идёт Иванов, и у него на спине бумажка торчит. За ним удобно следить.
Как вся эта ёперная катавасия работает:
- Грузчик (корутина) закончил таскать коробки (или накосячил и упал в люк). Что он делает? Он берёт эту накладную (
Future) со своей спины и пишет на ней результат: «10 коробок» или «Исключение: сломал ногу». - Потом он орет: «Эй, начальник, готово!» — и вешает эту заполненную накладную на специальную гвоздику у стола заведующего (вызывает коллбэк).
- Заведующий (цикл событий) видит: о, на гвоздике накладная от Иванова висит. Он снимает её, смотрит. Если там другой грузчик Пётр ждал, пока Иванов закончит, чтобы начать свою работу (
await task), то заведующий тычет Петра в бок: «Проснись, мудила! Иванов закончил, вот его накладная, иди работай дальше».
Пример, чтобы вообще всё встало на свои места:
import asyncio
async def fetch_data():
print("Начинаю загрузку данных...")
await asyncio.sleep(2) # Представь, что он пошёл на перекур на 2 часа
print("Данные загружены")
return {"data": 123} # Вернулся с перекура и написал на накладной: "Данные"
async def main():
print("Создаю задачу для fetch_data")
# 1. Берем корутину fetch_data, наряжаем её в жилетку с накладной (Task) и говорим: "Вали на работу!"
task = asyncio.create_task(fetch_data())
# 2. main() говорит: "Я подожду тут, пока этот чувак с накладной не закончит".
# По сути, он вешает на ту же гвоздику записку "Разбуди main, когда task готов".
result = await task
# 3. Цикл событий будит main, суёт ему в руки заполненную накладную от task.
print(f"Задача завершена: {task.done()}") # Проверяем, а не обманули ли нас
print(f"Полученный результат: {result}") # Читаем, что там грузчик написал
asyncio.run(main())
Короче, цикл событий не бегает за каждым грузчиком с вопросом «ну чё, как дела?». Он сидит, пьёт чай, и ждёт, когда они сами принесут ему заполненные бумажки. Вся хитрая жопа системы — в этих бумажках-накладных (Future), которые приколочены к работникам (Task).