Как обрабатывать таймауты в асинхронных задачах с помощью `asyncio.wait_for()`?

Для обработки таймаутов в асинхронных задачах с использованием `asyncio.wait_for()`, необходимо обернуть корутину в `asyncio.wait_for()`, указав время ожидания (timeout) в секундах. Если корутина не завершится в течение указанного времени, будет выброшено исключение `asyncio.TimeoutError`, которое можно обработать в блоке `try...except`.

Пример:

import asyncio

async def my_coroutine():
  await asyncio.sleep(5)
  return "Задача выполнена!"

async def main():
  try:
    result = await asyncio.wait_for(my_coroutine(), timeout=2)
    print(result)
  except asyncio.TimeoutError:
    print("Время ожидания истекло!")

asyncio.run(main())
  
В этом примере, если `my_coroutine()` не завершится за 2 секунды, будет поймано исключение `asyncio.TimeoutError`.

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

Если корутина завершится до истечения таймаута, asyncio.wait_for() вернет результат ее выполнения. Если же таймаут истечет, будет выброшено исключение asyncio.TimeoutError. Это исключение необходимо перехватывать и обрабатывать.

Пример:


import asyncio

async def long_running_task():
    """Эмулирует задачу, которая может занять много времени."""
    try:
        await asyncio.sleep(5)  # Имитируем долгую работу
        return "Задача успешно завершена!"
    except asyncio.CancelledError:
        return "Задача отменена"

async def main():
    try:
        result = await asyncio.wait_for(long_running_task(), timeout=2) # Устанавливаем таймаут в 2 секунды
        print(f"Результат: {result}")
    except asyncio.TimeoutError:
        print("Время ожидания истекло!")
        # Здесь можно выполнить действия, если задача не успела завершиться вовремя,
        # например, отменить задачу, попытаться ее перезапустить, или сообщить об ошибке.
        # Важно отметить, что если задача *не отменена* при таймауте, она *продолжит выполняться в фоне*.
    except asyncio.CancelledError:
        print("Задача была отменена внешне")

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

В этом примере:

  • long_running_task() – это корутина, представляющая собой долго выполняемую задачу. В данном случае, она просто ждет 5 секунд.
  • asyncio.wait_for(long_running_task(), timeout=2) – запускает long_running_task() и устанавливает таймаут в 2 секунды.
  • Блок try...except обрабатывает возможное исключение asyncio.TimeoutError, которое будет выброшено, если long_running_task() не завершится в течение 2 секунд.
  • Обратите внимание на блок except asyncio.CancelledError. Если задача отменена вручную (например, вызвав task.cancel()), то будет выброшено это исключение, а не TimeoutError.

Важно: После таймаута, корутина long_running_task(), если она не обрабатывает исключение asyncio.CancelledError или иные, продолжит выполняться в фоновом режиме. Если необходимо прервать ее выполнение, нужно получить объект Task, представляющий запущенную корутину, и вызвать метод task.cancel(). Это вызовет исключение asyncio.CancelledError внутри корутины, которое нужно корректно обработать, чтобы избежать непредсказуемого поведения.

Пример отмены задачи после таймаута:


import asyncio

async def long_running_task():
    try:
        await asyncio.sleep(5)
        return "Задача успешно завершена!"
    except asyncio.CancelledError:
        print("Задача отменена внутри long_running_task")
        return "Задача отменена"


async def main():
    task = asyncio.create_task(long_running_task())  # Создаем объект Task
    try:
        result = await asyncio.wait_for(task, timeout=2)
        print(f"Результат: {result}")
    except asyncio.TimeoutError:
        print("Время ожидания истекло! Отменяем задачу...")
        task.cancel()  # Отменяем задачу
        try:
            await task  # Ждем завершения отмены задачи
        except asyncio.CancelledError:
            print("Задача успешно отменена")
    except asyncio.CancelledError:
        print("Задача была отменена внешне")



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

В этом улучшенном примере:

  • asyncio.create_task() создает объект Task, позволяющий контролировать запущенную корутину.
  • После срабатывания таймаута вызывается task.cancel() для отмены выполнения корутины.
  • await task в блоке except asyncio.TimeoutError ожидает фактического завершения отмены задачи. Без этого, задача может продолжать работать в фоне, даже после того, как код обработки таймаута завершится. await task необходимо поместить внутрь еще одного try...except блока, так как await на отмененной задаче, вызовет исключение asyncio.CancelledError
0