Futures и задачи в asyncio: создание, ожидание, отмена

Введение в Futures и задачи asyncio 🏁

В асинхронном Python ключевыми строительными блоками являются Futures и Tasks. Они позволяют эффективно управлять параллельным выполнением кода, не блокируя поток выполнения.

import asyncio

async def main():
    # Создаем Future вручную
    my_future = asyncio.Future()
    print(f"Future готов? {my_future.done()}")  # False

Futures представляют собой отложенные вычисления — это контейнеры для результата, который появится позже. Tasks — это подкласс Futures, обёртки вокруг корутин для их выполнения в event loop.


Создание задач: run_until_complete vs create_task ⚡

Есть два основных способа запуска корутин:

  1. loop.run_until_complete() — запускает корутину и ждёт её завершения
  2. loop.create_task() — планирует выполнение корутины без ожидания
async def fetch_data():
    print("Начинаем загрузку данных...")
    await asyncio.sleep(2)
    return "Данные загружены"

# Способ 1
result = await fetch_data()

# Способ 2
task = asyncio.create_task(fetch_data())

🔑 Ключевое отличие: create_task позволяет запускать несколько операций параллельно, а run_until_complete работает последовательно.


Ожидание нескольких задач одновременно 🤹

Для эффективной работы с несколькими задачами используйте asyncio.gather() или asyncio.wait():

async def task_one():
    await asyncio.sleep(1)
    return "Результат 1"

async def task_two():
    await asyncio.sleep(2)
    return "Результат 2"

# Вариант 1: gather
results = await asyncio.gather(task_one(), task_two())

# Вариант 2: wait
tasks = [task_one(), task_two()]
done, pending = await asyncio.wait(tasks)

📌 gather возвращает результаты в порядке вызова, а wait — множества завершённых и ожидающих задач.


Отмена задач и таймауты ⏳

В реальных приложениях часто требуется: 1. Прерывать долгие операции 2. Задавать временные ограничения

async def long_operation():
    try:
        await asyncio.sleep(10)
        return "Успех"
    except asyncio.CancelledError:
        print("Операция отменена!")
        raise

# Создаем и отменяем задачу
task = asyncio.create_task(long_operation())
await asyncio.sleep(1)
task.cancel()

# Таймаут операции
try:
    result = await asyncio.wait_for(long_operation(), timeout=2.0)
except asyncio.TimeoutError:
    print("Время истекло!")

🚨 Важно: Всегда корректно обрабатывайте CancelledError в своих корутинах!


Практический пример: параллельные HTTP-запросы 🌐

Давайте соберём всё вместе в реалистичном сценарии:

import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
        'https://python.org',
        'https://yandex.ru',
        'https://google.com'
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        pages = await asyncio.gather(*tasks, return_exceptions=True)

        for url, content in zip(urls, pages):
            print(f"{url}: {len(content)} символов")

asyncio.run(main())

🔥 Что происходит:

  1. Создаётся сессия HTTP-клиента
  2. Запускаются параллельные задачи для каждого URL
  3. Результаты собираются автоматически
  4. Обрабатываются возможные ошибки (return_exceptions=True)

Продвинутые техники работы с Tasks 🎯

  1. Отслеживание прогресса: можно использовать callback-функции через add_done_callback
  2. Ограничение параллелизма: с помощью семафоров (asyncio.Semaphore)
  3. Обработка исключений: через метод exception() у Task
async def monitored_task():
    await asyncio.sleep(1)
    if random.random() > 0.5:
        raise ValueError("Что-то пошло не так!")

def callback(future):
    if future.exception():
        print(f"Ошибка в задаче: {future.exception()}")

task = asyncio.create_task(monitored_task())
task.add_done_callback(callback)

💡 Для более сложных сценариев рекомендую курс Данилы Бежина по asyncio на YouTube: https://www.youtube.com/@DanilaBezhin


Заключение и главные выводы 🏆

  1. Tasks — обёртки вокруг корутин для параллельного выполнения
  2. Futures — низкоуровневые объекты для отложенных вычислений
  3. Используйте gather для параллельного выполнения с сохранением порядка
  4. Всегда предусматривайте механизмы отмены и таймаутов
  5. Обрабатывайте исключения на уровне отдельных задач и всего приложения

Теперь вы готовы эффективно использовать асинхронность в Python! Попробуйте применить эти знания в своих проектах — результат вас впечатлит. 🚀

Скрыть рекламу навсегда

📘 VK Видео — обучение без ограничений

Все уроки доступны без VPN, без блокировок и зависаний.

Можно смотреть с телефона, планшета или компьютера — в любое время.

▶️ Смотреть на VK Видео