From b4dc0bb0c10fe7e46ba76c1bc0b0fc525d7ea2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Sep 2024 13:32:08 +0200 Subject: [PATCH 1/3] Document how to cancel an asynchronous task group. --- Doc/library/asyncio-task.rst | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index abf1726b34f539..ab6ef6acd1b6dc 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -414,6 +414,52 @@ reported by :meth:`asyncio.Task.cancelling`. Improved handling of simultaneous internal and external cancellations and correct preservation of cancellation counts. +Group Cancellation +------------------ + +Cancelling an entire task group is not natively supported by the standard +library but can be achieved by adding a task to the group that raises an +exception and ignoring that exception: + +.. code-block:: python + + import asyncio + from asyncio import TaskGroup + + class CancelTaskGroup(Exception): + """Exception raised to cancel a task group.""" + + async def job(task_id, sleep_time): + print(f'Task {task_id}: start') + await asyncio.sleep(sleep_time) + print(f'Task {task_id}: done') + + async def cancel_task_group(): + """A task that would cancel the group it belongs to.""" + raise CancelTaskGroup() + + async def main(): + try: + async with TaskGroup() as group: + # ... spawn some tasks ... + group.create_task(job(1, 1)) + group.create_task(job(2, 2)) + # create a task that would cancel the group after 1.5 seconds + await asyncio.sleep(1.5) + group.create_task(cancel_task_group()) + except* CancelTaskGroup: + pass + + asyncio.run(main()) + +Expected output: + +.. code-block:: text + + Task 1: start + Task 2: start + Task 1: done + Sleeping ======== From 4e1c6bcb555a3e954cf533f47e19ce7b31f7767c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:46:47 +0200 Subject: [PATCH 2/3] Update Doc/library/asyncio-task.rst --- Doc/library/asyncio-task.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index ab6ef6acd1b6dc..5775d0b1865b15 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -427,28 +427,28 @@ exception and ignoring that exception: from asyncio import TaskGroup class CancelTaskGroup(Exception): - """Exception raised to cancel a task group.""" + """Exception raised to cancel a task group.""" - async def job(task_id, sleep_time): - print(f'Task {task_id}: start') - await asyncio.sleep(sleep_time) - print(f'Task {task_id}: done') + async def stop_task_group(): + """A task that cancels the group it belongs to.""" + raise CancelTaskGroup() - async def cancel_task_group(): - """A task that would cancel the group it belongs to.""" - raise CancelTaskGroup() + async def job(task_id, sleep_time): + print(f'Task {task_id}: start') + await asyncio.sleep(sleep_time) + print(f'Task {task_id}: done') async def main(): try: - async with TaskGroup() as group: - # ... spawn some tasks ... - group.create_task(job(1, 1)) - group.create_task(job(2, 2)) - # create a task that would cancel the group after 1.5 seconds - await asyncio.sleep(1.5) - group.create_task(cancel_task_group()) + async with TaskGroup() as group: + # ... spawn some tasks ... + group.create_task(job(1, 1)) + group.create_task(job(2, 2)) + # create a task that cancels the group after 1.5 seconds + await asyncio.sleep(1.5) + group.create_task(stop_task_group()) except* CancelTaskGroup: - pass + pass asyncio.run(main()) From cf21e18b398482de7590e50e82cb855fe5abd5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:32:34 +0200 Subject: [PATCH 3/3] address review --- Doc/library/asyncio-task.rst | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 5775d0b1865b15..4716a3f9c8ac79 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -414,24 +414,24 @@ reported by :meth:`asyncio.Task.cancelling`. Improved handling of simultaneous internal and external cancellations and correct preservation of cancellation counts. -Group Cancellation ------------------- +Terminating a Task Group +------------------------ -Cancelling an entire task group is not natively supported by the standard -library but can be achieved by adding a task to the group that raises an -exception and ignoring that exception: +While terminating a task group is not natively supported by the standard +library, termination can be achieved by adding an exception-raising task +to the task group and ignoring the raised exception: .. code-block:: python import asyncio from asyncio import TaskGroup - class CancelTaskGroup(Exception): - """Exception raised to cancel a task group.""" + class TerminateTaskGroup(Exception): + """Exception raised to terminate a task group.""" - async def stop_task_group(): - """A task that cancels the group it belongs to.""" - raise CancelTaskGroup() + async def force_terminate_task_group(): + """Used to force termination of a task group.""" + raise TerminateTaskGroup() async def job(task_id, sleep_time): print(f'Task {task_id}: start') @@ -441,13 +441,14 @@ exception and ignoring that exception: async def main(): try: async with TaskGroup() as group: - # ... spawn some tasks ... - group.create_task(job(1, 1)) - group.create_task(job(2, 2)) - # create a task that cancels the group after 1.5 seconds - await asyncio.sleep(1.5) - group.create_task(stop_task_group()) - except* CancelTaskGroup: + # spawn some tasks + group.create_task(job(1, 0.5)) + group.create_task(job(2, 1.5)) + # sleep for 1 second + await asyncio.sleep(1) + # add an exception-raising task to force the group to terminate + group.create_task(force_terminate_task_group()) + except* TerminateTaskGroup: pass asyncio.run(main())