@@ -402,69 +402,85 @@ Example::
402402 task2 = tg.create_task(another_coro(...))
403403 print(f"Both tasks have completed now: {task1.result()}, {task2.result()}")
404404
405- The ``async with `` statement will wait for all tasks in the group to finish.
406- While waiting, new tasks may still be added to the group
407- (for example, by passing ``tg `` into one of the coroutines
408- and calling ``tg.create_task() `` in that coroutine). There is also opportunity to
409- request termination of the entire task group with ``tg.cancel() ``, based on some condition.
410- Once the last task has finished and the ``async with `` block is exited,
411- no new tasks may be added to the group.
412-
413- The first time any of the tasks belonging to the group fails
414- with an exception other than :exc: `asyncio.CancelledError `,
415- the remaining tasks in the group are cancelled.
416- No further tasks can then be added to the group.
417- At this point, if the body of the ``async with `` statement is still active
418- (i.e., :meth: `~object.__aexit__ ` hasn't been called yet),
419- the task directly containing the ``async with `` statement is also cancelled.
420- The resulting :exc: `asyncio.CancelledError ` will interrupt an ``await ``,
421- but it will not bubble out of the containing ``async with `` statement.
422-
423- Once all tasks have finished, if any tasks have failed
424- with an exception other than :exc: `asyncio.CancelledError `,
425- those exceptions are combined in an
426- :exc: `ExceptionGroup ` or :exc: `BaseExceptionGroup `
427- (as appropriate; see their documentation)
428- which is then raised.
429-
430- Two base exceptions are treated specially:
431- If any task fails with :exc: `KeyboardInterrupt ` or :exc: `SystemExit `,
432- the task group still cancels the remaining tasks and waits for them,
433- but then the initial :exc: `KeyboardInterrupt ` or :exc: `SystemExit `
434- is re-raised instead of :exc: `ExceptionGroup ` or :exc: `BaseExceptionGroup `.
435-
436- If the body of the ``async with `` statement exits with an exception
437- (so :meth: `~object.__aexit__ ` is called with an exception set),
438- this is treated the same as if one of the tasks failed:
439- the remaining tasks are cancelled and then waited for,
440- and non-cancellation exceptions are grouped into an
441- exception group and raised.
442- The exception passed into :meth: `~object.__aexit__ `,
443- unless it is :exc: `asyncio.CancelledError `,
444- is also included in the exception group.
445- The same special case is made for
446- :exc: `KeyboardInterrupt ` and :exc: `SystemExit ` as in the previous paragraph.
447- There is an additional special case made only for the body of the
448- ``async with ``: if it raises :exc: `GeneratorExit ` and none of the
449- other tasks raise exceptions that would be reported, then the
450- :exc: `GeneratorExit ` is reraised.
451-
452- Task groups are careful not to mix up the internal cancellation used to
453- "wake up" their :meth: `~object.__aexit__ ` with cancellation requests
454- for the task in which they are running made by other parties.
405+ A few points to keep in mind when using task groups:
406+
407+ * The ``async with `` statement will wait for all tasks in the group
408+ to finish. While waiting, new tasks may still be added to the group
409+ (for example, by passing ``tg `` into one of the coroutines and
410+ calling ``tg.create_task() `` in that coroutine); once the last task
411+ has finished and the ``async with `` block is exited, no new tasks
412+ may be added.
413+
414+ * Termination of the entire task group may be requested with
415+ ``tg.cancel() ``, based on some condition.
416+
417+ * If the group is shut down (e.g. because another task failed) before
418+ a newly created task has started running, the task is cancelled
419+ without its coroutine executing at all, not even to its first
420+ ``await ``. To guarantee that the coroutine starts, create the task
421+ eagerly with ``eager_start=True `` or use
422+ :func: `asyncio.eager_task_factory `. For example::
423+
424+ async def job():
425+ print("job started") # never printed
426+ try:
427+ await asyncio.sleep(1)
428+ finally:
429+ print("job cleaned up") # never printed
430+
431+ async def main():
432+ async with asyncio.TaskGroup() as tg:
433+ tg.create_task(job())
434+ raise RuntimeError # shuts down the group before job() runs
435+
436+ With ``tg.create_task(job(), eager_start=True) ``, ``job() `` runs up
437+ to the ``await ``, is cancelled there, and both messages are printed.
438+
439+ When any of the tasks belonging to the group fails with an exception
440+ other than :exc: `asyncio.CancelledError ` (or the body of the
441+ ``async with `` statement exits with an exception, which is treated
442+ the same way):
443+
444+ * The first time this happens, the remaining tasks in the group are
445+ cancelled and then waited for, and no further tasks can be added to
446+ the group. If the body of the ``async with `` statement is still
447+ active (i.e., :meth: `~object.__aexit__ ` hasn't been called yet),
448+ the task directly containing the ``async with `` statement is also
449+ cancelled. The resulting :exc: `asyncio.CancelledError ` will
450+ interrupt an ``await ``, but it will not bubble out of the containing
451+ ``async with `` statement.
452+
453+ * Once all tasks have finished, the non-cancellation exceptions --
454+ including the exception the body exited with, unless it is
455+ :exc: `asyncio.CancelledError ` -- are combined in an
456+ :exc: `ExceptionGroup ` or :exc: `BaseExceptionGroup `
457+ (as appropriate; see their documentation), which is then raised.
458+
459+ * Some exceptions are treated specially: if any task fails with
460+ :exc: `KeyboardInterrupt ` or :exc: `SystemExit `, the task group still
461+ cancels the remaining tasks and waits for them, but then the initial
462+ :exc: `KeyboardInterrupt ` or :exc: `SystemExit ` is re-raised instead
463+ of :exc: `ExceptionGroup ` or :exc: `BaseExceptionGroup `.
464+ Additionally, if the body of the ``async with `` statement raises
465+ :exc: `GeneratorExit ` and none of the other tasks raise exceptions
466+ that would be reported, the :exc: `GeneratorExit ` is re-raised.
467+
468+ Task groups are careful not to mix up the internal cancellation used
469+ to "wake up" their :meth: `~object.__aexit__ ` with cancellation
470+ requests for the task in which they are running made by other parties.
455471In particular, when one task group is syntactically nested in another,
456- and both experience an exception in one of their child tasks simultaneously,
457- the inner task group will process its exceptions, and then the outer task group
458- will receive another cancellation and process its own exceptions.
472+ and both experience an exception in one of their child tasks
473+ simultaneously, the inner task group will process its exceptions, and
474+ then the outer task group will receive another cancellation and
475+ process its own exceptions.
459476
460477In the case where a task group is cancelled externally and also must
461478raise an :exc: `ExceptionGroup `, it will call the parent task's
462- :meth: `~asyncio.Task.cancel ` method. This ensures that a
479+ :meth: `~asyncio.Task.cancel ` method. This ensures that a
463480:exc: `asyncio.CancelledError ` will be raised at the next
464- :keyword: `await `, so the cancellation is not lost.
465-
466- Task groups preserve the cancellation count
467- reported by :meth: `asyncio.Task.cancelling `.
481+ :keyword: `await `, so the cancellation is not lost. Task groups also
482+ preserve the cancellation count reported by
483+ :meth: `asyncio.Task.cancelling `.
468484
469485.. versionchanged :: 3.13
470486
0 commit comments