Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

asyncio.exceptions.CancelledError in Python AsyncIO Tasks

Verified FixPython 3.10+Python Native AsyncIOSilo: asyncio

Quick Fix / Solution Rapide

When catching asyncio.CancelledError for cleanup in a coroutine, always re-raise it (raise) so the event loop knows the task cancellation succeeded, or use asyncio.shield() to protect critical operations.

Root Cause Analysis

This error occurs when an asyncio task or coroutine is cancelled from the outside via task.cancel() or an enclosing asyncio.wait_for() timeout, causing Python to inject asyncio.CancelledError into the awaiting coroutine at its next await point.

The Lifecycle of AsyncIO Task Cancellation

In Python's asynchronous model, cooperative multitasking requires tasks to be interrupted cleanly. When task.cancel() is called:

  1. The task is marked as cancelled in the event loop queue.
  2. When the coroutine reaches its next await expression, the event loop raises asyncio.CancelledError inside the coroutine frame.
  3. In Python 3.8+, CancelledError inherits directly from BaseException (like KeyboardInterrupt), not Exception, to prevent generic except Exception: blocks from accidentally swallowing cancellations.

How the Exception Leaks

  • Uncaught Task Exceptions: Calling await task after it was cancelled without handling the cancellation.
  • Timeouts in asyncio.wait_for(): When a timeout triggers, wait_for cancels the inner task and raises TimeoutError.
  • Event Loop Shutdown: When the application receives a SIGINT/SIGTERM, graceful shutdown routines cancel all pending background tasks.

Reproduction Code (MCVE)

Example: Bug Reproduction
import asyncio

async def long_running_task():
    await asyncio.sleep(5)

async def runner():
    task = asyncio.create_task(long_running_task())
    await asyncio.sleep(0.01)
    task.cancel()
    # Awaiting a cancelled task raises asyncio.CancelledError
    await task

asyncio.run(runner())

Solution 1: Clean Up Resources and Re-Raise `CancelledError`

Perform cleanup inside finally or except asyncio.CancelledError: and re-raise the exception to allow clean task termination.

Example: Recommended Solution
import asyncio

async def resilient_worker():
    try:
        print('Worker started running...')
        await asyncio.sleep(0.05)
    except asyncio.CancelledError:
        print('Cancellation requested: closing sockets and flushing buffers.')
        # Always re-raise to complete task cancellation protocol
        raise
    finally:
        print('Final cleanup executed.')

async def main():
    task = asyncio.create_task(resilient_worker())
    await asyncio.sleep(0.01)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print('Parent successfully handled child task cancellation.')

asyncio.run(main())

Solution 2: Protect Critical Database Writes with `asyncio.shield()`

Use asyncio.shield() to prevent a crucial background operation (such as a database commit) from being aborted if the outer request times out.

Example: Alternative Solution
import asyncio

async def critical_database_write():
    print('Beginning critical transactional write...')
    await asyncio.sleep(0.02)
    print('Critical write saved to disk.')

async def handle_request():
    # Shield protects critical_database_write from cancellation by outer timeouts
    await asyncio.shield(critical_database_write())

asyncio.run(handle_request())

Common Pitfalls & Python 3.8+ Changes

A dangerous anti-pattern is swallowing CancelledError without re-raising (except asyncio.CancelledError: pass). In Python 3.8+, if a coroutine swallows CancelledError and returns a normal value, the task is considered non-cancellable, and the event loop will raise RuntimeError: Task was cancelled but returned a value.

Contrasting CancelledError with TimeoutError:

  • CancelledError: Raised inside the cancelled task to stop execution.
  • TimeoutError: Raised by asyncio.wait_for() to the caller when the duration limit is reached.