Cancellation in asyncio

Asyncio cancellation is a cooperative request delivered to a task as `CancelledError`, usually when the coroutine next resumes. Handle cleanup during unwinding and normally propagate the exception so callers can observe that the task stopped.

Asyncio cancellation is cooperative: calling `task.cancel()` requests that the task stop, but does not forcibly terminate its coroutine. The task gets an opportunity to handle the request when it next resumes.

At an appropriate point, asyncio raises `asyncio.CancelledError` inside the coroutine. This commonly happens while the task is awaiting an operation. Code that does not catch the exception will unwind, running any applicable `finally` blocks.

A coroutine can catch `CancelledError` to perform cleanup or other limited handling. It should normally re-raise the exception afterward so callers and structured concurrency mechanisms can observe that cancellation took place. Swallowing it can leave a task running when its caller expects it to stop.

`Task.cancel()` returning `True` means a cancellation request was accepted, not that the task has finished cancelling. Await the task when you need to wait for its termination. `task.cancelled()` is true only after the task has completed by propagating cancellation.

Cancellation is also how many asyncio timeout patterns stop work that has exceeded a deadline. Cleanup should be prompt and should not indefinitely block cancellation. Code using shielding or suppressing cancellation needs a clear policy for what happens to any work that continues.

Key points

Interview questions

What does `task.cancel()` do?

It requests that the task be cancelled; the coroutine is not immediately killed. When the task next resumes, asyncio raises `CancelledError` in it. The coroutine can run cleanup while unwinding, and it can catch the exception. If it suppresses cancellation, the task may continue instead of finishing as cancelled.

How should a coroutine handle cancellation while cleaning up resources?

A `finally` block runs as the coroutine unwinds after `CancelledError` is raised. If a coroutine catches `CancelledError`, it can perform cleanup and then re-raise the same exception. Re-raising preserves the cancellation signal for code awaiting the task. Cleanup should be bounded so it does not prevent the task from stopping indefinitely.

Does a `True` result from `task.cancel()` mean the task is cancelled?

`Task.cancel()` returning `True` says the request was accepted, not that the task has completed. The task may not yet have resumed to receive `CancelledError`, or it may handle that exception. Await the task to observe its completion, catching `CancelledError` if cancellation is expected. Afterward, `task.cancelled()` reports whether it completed by propagating cancellation.

What does `asyncio.shield()` change about cancellation?

`asyncio.shield()` stops cancellation of the current outer await from being forwarded to the protected awaitable. The outer task still receives `CancelledError`, so shielding does not make the caller uncancellable. The protected task may keep running and must still be managed, awaited, or otherwise cleaned up. Shielding is useful only when continuing that work after caller cancellation is intentional.