Task Scope

class ShieldScope(timeout: float | None = None)[source]

A context-manager to make the codes within the scope to be shielded from cancellation, delaying any cancellation attempts in the middle to be re-raised afterwards. You may use it as an async context manager as well.

See https://github.com/python/cpython/issues/99714#issuecomment-1817941789 for the original ideation.

To self-cancel from the inside of ShieldScope, you may simply raise asyncio.CancelledError.

async def work():
    try:
        await some_job()
    finally:
        with ShieldScope():
            await cleanup()  # ensured to run regardless of cancellation timing

task = asyncio.create_task(work())
...
await cancel_and_wait(task)

It may be used as either an async context manager or a context manager. As an async context manager, it is equivalent to TaskScope(shield=True). As a (sync) coontext manager, you cannot spawn child tasks because it does not wait for children’s completion when exiting the context scope. You may use it as a simple block marker to wrap the code lines to be shielded.

When there are multiple nested ShieldScope combined with TaskScope, the cancellation is deferred to the point of exit of the topmost ShieldScope (or TaskScop(shield=True)) block where the parent scope is not shielded or there is no parent scope.

Added in version 2.1.

Changed in version 2.2: Moved from aiotools.cancel module to aiotools.taskscope module to subclass TaskScope without circular imports. The root import path aiotools.ShieldScope is preserved.

Changed in version 2.2: When used as a synchronous context manager, spawning child tasks is explicitly prohibited.

create_task(coro: Coroutine[Any, Any, T], *, name: str | None = None, context: Context | None = None, **kwargs: Any) Task[T][source]

Create a new task in this scope and return it. Similar to asyncio.create_task().

class TaskScope(*, shield: bool = False, timeout: float | None = None, exception_handler: Callable[[ErrorArg], None] | LoopExceptionHandler | None = LoopExceptionHandler.TOKEN, context: Context | None = None)[source]

TaskScope is an asynchronous context manager which implements structured concurrency, i.e., “scoped” cancellation, over a set of child tasks. It terminates when all child tasks make conclusion (either results or exceptions).

TaskScope subclasses TaskContext, but it mandates use of async with blocks to clarify which task is the parent of the child tasks spawned via create_task().

The key difference to asyncio.TaskGroup is that it allows customization of the exception handling logic for unhandled child task exceptions, instead cancelling all pending child tasks upon any unhandled child task exceptions and collecting them as an ExceptionGroup.

Since TaskScope may be used for a long-running server context, unhandled child exceptions are NOT stored at all, but passed to the exception handler directly and immediately. If you want to collect results and exceptions, please use as_completed_safe() or gather_safe().

When shield=True, the scope is protected from outside cancellations, while re-raising the requested cancellations when terminated. From inside, you may simply raise a new asyncio.CancelledError from the context manager body to self-cancel the shielded scope.

You may nest and mix multiple TaskScope and even asyncio.TaskGroup within a single task or via subtask chains. In such cases, cancelling the outmost parent task will be shielded by the topmost TaskScope with shield=True or ShieldScope.

The timeout argument enforces timeout even when the scope is shielded, unlike the vanilla asyncio.timeout().

Refer TaskContext for the descriptions about the constructor arguments.

Based on this customizability, Supervisor is a mere alias of TaskScope with exception_handler=None.

Added in version 2.0.

Changed in version 2.1: In Python 3.14 or higher, it also updates the asyncio call graph so that the task awaiter could be tracked down via TaskScope, like asyncio.TaskGroup.

Changed in version 2.1: Added the shield and timeout options.

Changed in version 2.2: Now nesting and mixing TaskScope and TaskGroup is fully supported and behaves consistently, by deferring the cancellation at the topmost shielded scope.

abort(msg: str | None = None) None[source]

Triggers cancellation of the scope and all its children and immediately returns without waiting for completion. This method ignores the shield option.

async aclose() None[source]

Triggers cancellation of the scope and all its children, then waits for completion. This method ignores the shield option.

Calling this method will cancel the host task and the task body will observe a asyncio.CancelledError.

create_task(coro: Coroutine[Any, Any, T], *, name: str | None = None, context: Context | None = None, **kwargs: Any) Task[T][source]

Create a new task in this scope and return it. Similar to asyncio.create_task().

move_on_after(timeout: float | None = None, shield: bool = False) AsyncIterator[TaskScope][source]

A shortcut to create a TaskScope with a timeout while ignoring and continuing after timeout.

prior_work()
async with move_on_after(3.0) as ts:
    ts.create_task(...)
    await some_work()
# after 3.0 seconds, any remaining coroutines/tasks within the scope is cancelled,
# and the control resumes here.
after_work()

Added in version 2.1.