https://github.com/python/cpython/commit/862befdb99c8268f570c28adaa342eb6586fa0dc
commit: 862befdb99c8268f570c28adaa342eb6586fa0dc
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-21T22:31:01+05:30
summary:
gh-116048: restructure task group docs and add docs for eager execution
(#156102)
files:
M Doc/library/asyncio-task.rst
diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index c6f0fb8a50917a..689a6a6d2e0bd9 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -402,69 +402,85 @@ Example::
task2 = tg.create_task(another_coro(...))
print(f"Both tasks have completed now: {task1.result()},
{task2.result()}")
-The ``async with`` statement will wait for all tasks in the group to finish.
-While waiting, new tasks may still be added to the group
-(for example, by passing ``tg`` into one of the coroutines
-and calling ``tg.create_task()`` in that coroutine). There is also
opportunity to
-request termination of the entire task group with ``tg.cancel()``, based on
some condition.
-Once the last task has finished and the ``async with`` block is exited,
-no new tasks may be added to the group.
-
-The first time any of the tasks belonging to the group fails
-with an exception other than :exc:`asyncio.CancelledError`,
-the remaining tasks in the group are cancelled.
-No further tasks can then be added to the group.
-At this point, if the body of the ``async with`` statement is still active
-(i.e., :meth:`~object.__aexit__` hasn't been called yet),
-the task directly containing the ``async with`` statement is also cancelled.
-The resulting :exc:`asyncio.CancelledError` will interrupt an ``await``,
-but it will not bubble out of the containing ``async with`` statement.
-
-Once all tasks have finished, if any tasks have failed
-with an exception other than :exc:`asyncio.CancelledError`,
-those exceptions are combined in an
-:exc:`ExceptionGroup` or :exc:`BaseExceptionGroup`
-(as appropriate; see their documentation)
-which is then raised.
-
-Two base exceptions are treated specially:
-If any task fails with :exc:`KeyboardInterrupt` or :exc:`SystemExit`,
-the task group still cancels the remaining tasks and waits for them,
-but then the initial :exc:`KeyboardInterrupt` or :exc:`SystemExit`
-is re-raised instead of :exc:`ExceptionGroup` or :exc:`BaseExceptionGroup`.
-
-If the body of the ``async with`` statement exits with an exception
-(so :meth:`~object.__aexit__` is called with an exception set),
-this is treated the same as if one of the tasks failed:
-the remaining tasks are cancelled and then waited for,
-and non-cancellation exceptions are grouped into an
-exception group and raised.
-The exception passed into :meth:`~object.__aexit__`,
-unless it is :exc:`asyncio.CancelledError`,
-is also included in the exception group.
-The same special case is made for
-:exc:`KeyboardInterrupt` and :exc:`SystemExit` as in the previous paragraph.
-There is an additional special case made only for the body of the
-``async with``: if it raises :exc:`GeneratorExit` and none of the
-other tasks raise exceptions that would be reported, then the
-:exc:`GeneratorExit` is reraised.
-
-Task groups are careful not to mix up the internal cancellation used to
-"wake up" their :meth:`~object.__aexit__` with cancellation requests
-for the task in which they are running made by other parties.
+A few points to keep in mind when using task groups:
+
+* The ``async with`` statement will wait for all tasks in the group
+ to finish. While waiting, new tasks may still be added to the group
+ (for example, by passing ``tg`` into one of the coroutines and
+ calling ``tg.create_task()`` in that coroutine); once the last task
+ has finished and the ``async with`` block is exited, no new tasks
+ may be added.
+
+* Termination of the entire task group may be requested with
+ ``tg.cancel()``, based on some condition.
+
+* If the group is shut down (e.g. because another task failed) before
+ a newly created task has started running, the task is cancelled
+ without its coroutine executing at all, not even to its first
+ ``await``. To guarantee that the coroutine starts, create the task
+ eagerly with ``eager_start=True`` or use
+ :func:`asyncio.eager_task_factory`. For example::
+
+ async def job():
+ print("job started") # never printed
+ try:
+ await asyncio.sleep(1)
+ finally:
+ print("job cleaned up") # never printed
+
+ async def main():
+ async with asyncio.TaskGroup() as tg:
+ tg.create_task(job())
+ raise RuntimeError # shuts down the group before job() runs
+
+ With ``tg.create_task(job(), eager_start=True)``, ``job()`` runs up
+ to the ``await``, is cancelled there, and both messages are printed.
+
+When any of the tasks belonging to the group fails with an exception
+other than :exc:`asyncio.CancelledError` (or the body of the
+``async with`` statement exits with an exception, which is treated
+the same way):
+
+* The first time this happens, the remaining tasks in the group are
+ cancelled and then waited for, and no further tasks can be added to
+ the group. If the body of the ``async with`` statement is still
+ active (i.e., :meth:`~object.__aexit__` hasn't been called yet),
+ the task directly containing the ``async with`` statement is also
+ cancelled. The resulting :exc:`asyncio.CancelledError` will
+ interrupt an ``await``, but it will not bubble out of the containing
+ ``async with`` statement.
+
+* Once all tasks have finished, the non-cancellation exceptions --
+ including the exception the body exited with, unless it is
+ :exc:`asyncio.CancelledError` -- are combined in an
+ :exc:`ExceptionGroup` or :exc:`BaseExceptionGroup`
+ (as appropriate; see their documentation), which is then raised.
+
+* Some exceptions are treated specially: if any task fails with
+ :exc:`KeyboardInterrupt` or :exc:`SystemExit`, the task group still
+ cancels the remaining tasks and waits for them, but then the initial
+ :exc:`KeyboardInterrupt` or :exc:`SystemExit` is re-raised instead
+ of :exc:`ExceptionGroup` or :exc:`BaseExceptionGroup`.
+ Additionally, if the body of the ``async with`` statement raises
+ :exc:`GeneratorExit` and none of the other tasks raise exceptions
+ that would be reported, the :exc:`GeneratorExit` is re-raised.
+
+Task groups are careful not to mix up the internal cancellation used
+to "wake up" their :meth:`~object.__aexit__` with cancellation
+requests for the task in which they are running made by other parties.
In particular, when one task group is syntactically nested in another,
-and both experience an exception in one of their child tasks simultaneously,
-the inner task group will process its exceptions, and then the outer task group
-will receive another cancellation and process its own exceptions.
+and both experience an exception in one of their child tasks
+simultaneously, the inner task group will process its exceptions, and
+then the outer task group will receive another cancellation and
+process its own exceptions.
In the case where a task group is cancelled externally and also must
raise an :exc:`ExceptionGroup`, it will call the parent task's
-:meth:`~asyncio.Task.cancel` method. This ensures that a
+:meth:`~asyncio.Task.cancel` method. This ensures that a
:exc:`asyncio.CancelledError` will be raised at the next
-:keyword:`await`, so the cancellation is not lost.
-
-Task groups preserve the cancellation count
-reported by :meth:`asyncio.Task.cancelling`.
+:keyword:`await`, so the cancellation is not lost. Task groups also
+preserve the cancellation count reported by
+:meth:`asyncio.Task.cancelling`.
.. versionchanged:: 3.13
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]