← Назад к вопросам

Как исключение в одной задаче повлияет на другие задачи в asyncio?

3.0 Senior🔥 181 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Исключения в asyncio

Это одна из самых важных и часто неправильно понимаемых частей asyncio. Хочу дать вам полный ответ с примерами.

Главный принцип: Задачи изолированы

Исключение в одной задаче НЕ влияет на другие задачи при условии, что вы правильно обрабатываете ошибки. Каждая задача (task) имеет свой контекст исполнения и управляется независимо.

Однако есть критические случаи, когда исключение может повлиять на всё приложение.

Сценарий 1: Необработанное исключение в фоновой задаче

import asyncio

async def task_with_error():
    await asyncio.sleep(0.1)
    raise ValueError("Ошибка в задаче 1")

async def normal_task():
    for i in range(5):
        print(f"Задача 2: итерация {i}")
        await asyncio.sleep(0.2)

async def main():
    # Создаём задачи
    task1 = asyncio.create_task(task_with_error())
    task2 = asyncio.create_task(normal_task())
    
    # Ждём обеих
    results = await asyncio.gather(task1, task2)

if __name__ == "__main__":
    asyncio.run(main())

Результат: asyncio.gather() вызовет исключение, и обе задачи остановятся. Это не изоляция, а отказ при ошибке.

Сценарий 2: Правильная обработка с gather(..., return_exceptions=True)

import asyncio

async def task_with_error():
    await asyncio.sleep(0.1)
    raise ValueError("Ошибка в задаче 1")

async def normal_task():
    for i in range(3):
        print(f"Задача 2: итерация {i}")
        await asyncio.sleep(0.2)
    return "Успех"

async def main():
    task1 = asyncio.create_task(task_with_error())
    task2 = asyncio.create_task(normal_task())
    
    # return_exceptions=True — исключение становится результатом
    results = await asyncio.gather(task1, task2, return_exceptions=True)
    
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            print(f"Задача {i+1} упала с ошибкой: {result}")
        else:
            print(f"Задача {i+1} завершилась: {result}")

if __name__ == "__main__":
    asyncio.run(main())

Вывод:

Задача 2: итерация 0
Задача 2: итерация 1
Задача 2: итерация 2
Задача 1 упала с ошибкой: Ошибка в задаче 1
Задача 2 завершилась: Успех

Задача 2 полностью независима от ошибки в задаче 1.

Сценарий 3: Независимое выполнение без gather

import asyncio

async def task_with_error():
    try:
        await asyncio.sleep(0.1)
        raise ValueError("Ошибка в задаче 1")
    except ValueError as e:
        print(f"Обработали: {e}")

async def normal_task():
    for i in range(3):
        print(f"Задача 2: итерация {i}")
        await asyncio.sleep(0.2)

async def main():
    # Запускаем задачи независимо
    task1 = asyncio.create_task(task_with_error())
    task2 = asyncio.create_task(normal_task())
    
    # Не ждём результаты, просто даём им выполняться
    await asyncio.sleep(1)  # Даём время на выполнение

if __name__ == "__main__":
    asyncio.run(main())

Критический случай: Исключение в event loop

import asyncio

def blocking_operation():
    raise RuntimeError("Ошибка в синхронном коде")

async def async_task():
    print("Я всё ещё выполняюсь")
    await asyncio.sleep(1)

async def main():
    task = asyncio.create_task(async_task())
    
    # Синхронное исключение ОСТАНАВЛИВАЕТ весь event loop
    blocking_operation()

if __name__ == "__main__":
    asyncio.run(main())

Вывод: Синхронное исключение разрушает весь event loop и все задачи прерываются.

Best Practices

1. Всегда оборачивайте create_task() в try-except:

try:
    await asyncio.gather(task1, task2, return_exceptions=True)
except Exception as e:
    logger.error(f"Ошибка: {e}")

2. Используйте return_exceptions=True для независимых задач:

results = await asyncio.gather(*tasks, return_exceptions=True)

3. Обрабатывайте исключения внутри коrutine:

async def safe_task():
    try:
        # ваш код
        pass
    except Exception as e:
        logger.error(f"Ошибка в задаче: {e}")

4. Не блокируйте event loop:

# Плохо
time.sleep(1)

# Хорошо
await asyncio.sleep(1)

Вывод

Задачи в asyncio полностью независимы при правильной обработке ошибок. Исключение в одной задаче влияет на другие только если:

  1. Вы используете gather() без return_exceptions=True
  2. Исключение возникает в синхронном коде внутри event loop
  3. Вы явно не обрабатываете ошибку

Основное правило: Всегда обрабатывайте исключения либо внутри corutine, либо используйте return_exceptions=True в gather().