Backend Development 7 min read

Managing and Cancelling Asyncio Tasks and Sub‑coroutines in Python

This article explains how to cancel asyncio tasks, invoke sub‑coroutines, cancel sub‑coroutines, handle multiple task cancellations, and manage errors in Python's asynchronous programming, providing clear code examples for each scenario and demonstrating best practices for robust async code.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Managing and Cancelling Asyncio Tasks and Sub‑coroutines in Python

Introduction In Python asynchronous programming, the asyncio library offers various ways to manage and cancel tasks, as well as to call and control sub‑coroutines.

1. Cancel Task The basic method to cancel a task is to call Task.cancel() . The following example shows how to create a task, wait briefly, then cancel it and handle the CancelledError :

import asyncio

async def my_coroutine():
    print("Coroutine started")
    await asyncio.sleep(2)
    print("Coroutine finished")

async def main():
    task = asyncio.create_task(my_coroutine())
    await asyncio.sleep(1)
    print("Cancelling the task...")
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("Task cancelled")

asyncio.run(main())

2. Sub‑coroutine Call Sub‑coroutines can be invoked directly (without awaiting) or with await to wait for completion. Two examples illustrate both approaches.

Direct call (no await):

import asyncio

async def sub_coroutine():
    print("Sub-coroutine started")
    await asyncio.sleep(1)
    print("Sub-coroutine finished")

async def main_coroutine():
    print("Main-coroutine started")
    sub_coroutine()  # Direct call, does not wait
    await asyncio.sleep(2)
    print("Main-coroutine finished")

asyncio.run(main_coroutine())

Using await (waits for sub‑coroutine):

import asyncio

async def sub_coroutine():
    print("Sub-coroutine started")
    await asyncio.sleep(1)
    print("Sub-coroutine finished")

async def main_coroutine():
    print("Main-coroutine started")
    await sub_coroutine()  # Await ensures completion
    print("Main-coroutine finished")

asyncio.run(main_coroutine())

3. Cancel Sub‑coroutine A sub‑coroutine can be cancelled from the main coroutine using Task.cancel() . The example demonstrates creating a task for the sub‑coroutine, cancelling it after a short delay, and handling the cancellation.

import asyncio

async def sub_coroutine(task):
    print("Sub-coroutine started")
    try:
        await asyncio.sleep(3)
        print("Sub-coroutine finished")
    except asyncio.CancelledError:
        print("Sub-coroutine cancelled")

async def main_coroutine():
    print("Main-coroutine started")
    sub_task = asyncio.create_task(sub_coroutine(asyncio.current_task()))
    await asyncio.sleep(1)
    print("Cancelling sub-coroutine...")
    sub_task.cancel()
    try:
        await sub_task
    except asyncio.CancelledError:
        print("Sub-coroutine cancelled")
    print("Main-coroutine finished")

asyncio.run(main_coroutine())

4. Cancel Multiple Tasks When several sub‑coroutines need to be cancelled, iterate over the list of tasks and call cancel() on each, then gather results with return_exceptions=True :

import asyncio

async def sub_coroutine(task):
    print(f"Sub-coroutine {task.get_name()} started")
    try:
        await asyncio.sleep(3)
        print(f"Sub-coroutine {task.get_name()} finished")
    except asyncio.CancelledError:
        print(f"Sub-coroutine {task.get_name()} cancelled")

async def main_coroutine():
    print("Main-coroutine started")
    tasks = [
        asyncio.create_task(sub_coroutine(asyncio.current_task()), name="Task-1"),
        asyncio.create_task(sub_coroutine(asyncio.current_task()), name="Task-2"),
        asyncio.create_task(sub_coroutine(asyncio.current_task()), name="Task-3")
    ]
    await asyncio.sleep(1)
    print("Cancelling all tasks...")
    for task in tasks:
        task.cancel()
    await asyncio.gather(*tasks, return_exceptions=True)
    print("Main-coroutine finished")

asyncio.run(main_coroutine())

5. Sub‑coroutine Error Handling To propagate and handle exceptions raised inside a sub‑coroutine, catch the exception, log it, and re‑raise so the main coroutine can process it:

import asyncio

async def sub_coroutine(task):
    print(f"Sub-coroutine {task.get_name()} started")
    try:
        await asyncio.sleep(1)
        raise ValueError("An error occurred")
    except Exception as e:
        print(f"Error in sub-coroutine {task.get_name()}: {e}")
        raise

async def main_coroutine():
    print("Main-coroutine started")
    tasks = [
        asyncio.create_task(sub_coroutine(asyncio.current_task()), name="Task-1"),
        asyncio.create_task(sub_coroutine(asyncio.current_task()), name="Task-2"),
        asyncio.create_task(sub_coroutine(asyncio.current_task()), name="Task-3")
    ]
    await asyncio.gather(*tasks, return_exceptions=True)
    print("Main-coroutine finished")

asyncio.run(main_coroutine())

6. Summary The article covered the principles and practical code for cancelling asyncio tasks, invoking sub‑coroutines with and without await , cancelling sub‑coroutines, cancelling multiple tasks simultaneously, and handling exceptions raised by sub‑coroutines.

Asynchronous Programmingasynciosub-coroutinetask cancellation
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.