Skip to content

Commit b6ec6d4

Browse files
GH-90908: Document asyncio.TaskGroup (GH-94359)
Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM>
1 parent 67d208f commit b6ec6d4

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

Doc/library/asyncio-task.rst

+101-2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,29 @@ To actually run a coroutine, asyncio provides three main mechanisms:
103103
world
104104
finished at 17:14:34
105105

106+
* The :class:`asyncio.TaskGroup` class provides a more modern
107+
alternative to :func:`create_task`.
108+
Using this API, the last example becomes::
109+
110+
async def main():
111+
async with asyncio.TaskGroup() as tg:
112+
task1 = tg.create_task(
113+
say_after(1, 'hello'))
114+
115+
task2 = tg.create_task(
116+
say_after(2, 'world'))
117+
118+
print(f"started at {time.strftime('%X')}")
119+
120+
# The wait is implicit when the context manager exits.
121+
122+
print(f"finished at {time.strftime('%X')}")
123+
124+
The timing and output should be the same as for the previous version.
125+
126+
.. versionadded:: 3.11
127+
:class:`asyncio.TaskGroup`.
128+
106129

107130
.. _asyncio-awaitables:
108131

@@ -223,6 +246,11 @@ Creating Tasks
223246
:exc:`RuntimeError` is raised if there is no running loop in
224247
current thread.
225248

249+
.. note::
250+
251+
:meth:`asyncio.TaskGroup.create_task` is a newer alternative
252+
that allows for convenient waiting for a group of related tasks.
253+
226254
.. important::
227255

228256
Save a reference to the result of this function, to avoid
@@ -254,6 +282,76 @@ Creating Tasks
254282
Added the *context* parameter.
255283

256284

285+
Task Groups
286+
===========
287+
288+
Task groups combine a task creation API with a convenient
289+
and reliable way to wait for all tasks in the group to finish.
290+
291+
.. class:: TaskGroup()
292+
293+
An :ref:`asynchronous context manager <async-context-managers>`
294+
holding a group of tasks.
295+
Tasks can be added to the group using :meth:`create_task`.
296+
All tasks are awaited when the context manager exits.
297+
298+
.. versionadded:: 3.11
299+
300+
.. method:: create_task(coro, *, name=None, context=None)
301+
302+
Create a task in this task group.
303+
The signature matches that of :func:`asyncio.create_task`.
304+
305+
Example::
306+
307+
async def main():
308+
async with asyncio.TaskGroup() as tg:
309+
task1 = tg.create_task(some_coro(...))
310+
task2 = tg.create_task(another_coro(...))
311+
print("Both tasks have completed now.")
312+
313+
The ``async with`` statement will wait for all tasks in the group to finish.
314+
While waiting, new tasks may still be added to the group
315+
(for example, by passing ``tg`` into one of the coroutines
316+
and calling ``tg.create_task()`` in that coroutine).
317+
Once the last task has finished and the ``async with`` block is exited,
318+
no new tasks may be added to the group.
319+
320+
The first time any of the tasks belonging to the group fails
321+
with an exception other than :exc:`asyncio.CancelledError`,
322+
the remaining tasks in the group are cancelled.
323+
At this point, if the body of the ``async with`` statement is still active
324+
(i.e., :meth:`~object.__aexit__` hasn't been called yet),
325+
the task directly containing the ``async with`` statement is also cancelled.
326+
The resulting :exc:`asyncio.CancelledError` will interrupt an ``await``,
327+
but it will not bubble out of the containing ``async with`` statement.
328+
329+
Once all tasks have finished, if any tasks have failed
330+
with an exception other than :exc:`asyncio.CancelledError`,
331+
those exceptions are combined in an
332+
:exc:`ExceptionGroup` or :exc:`BaseExceptionGroup`
333+
(as appropriate; see their documentation)
334+
which is then raised.
335+
336+
Two base exceptions are treated specially:
337+
If any task fails with :exc:`KeyboardInterrupt` or :exc:`SystemExit`,
338+
the task group still cancels the remaining tasks and waits for them,
339+
but then the initial :exc:`KeyboardInterrupt` or :exc:`SystemExit`
340+
is re-raised instead of :exc:`ExceptionGroup` or :exc:`BaseExceptionGroup`.
341+
342+
If the body of the ``async with`` statement exits with an exception
343+
(so :meth:`~object.__aexit__` is called with an exception set),
344+
this is treated the same as if one of the tasks failed:
345+
the remaining tasks are cancelled and then waited for,
346+
and non-cancellation exceptions are grouped into an
347+
exception group and raised.
348+
The exception passed into :meth:`~object.__aexit__`,
349+
unless it is :exc:`asyncio.CancelledError`,
350+
is also included in the exception group.
351+
The same special case is made for
352+
:exc:`KeyboardInterrupt` and :exc:`SystemExit` as in the previous paragraph.
353+
354+
257355
Sleeping
258356
========
259357

@@ -327,8 +425,9 @@ Running Tasks Concurrently
327425
cancellation of one submitted Task/Future to cause other
328426
Tasks/Futures to be cancelled.
329427

330-
.. versionchanged:: 3.10
331-
Removed the *loop* parameter.
428+
.. note::
429+
A more modern way to create and run tasks concurrently and
430+
wait for their completion is :class:`asyncio.TaskGroup`.
332431

333432
.. _asyncio_example_gather:
334433

Doc/whatsnew/3.11.rst

+5
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,11 @@ asyncio
443443
the asyncio library. (Contributed by Yves Duprat and Andrew Svetlov in
444444
:gh:`87518`.)
445445

446+
* Add :class:`~asyncio.TaskGroup` class,
447+
an :ref:`asynchronous context manager <async-context-managers>`
448+
holding a group of tasks that will wait for all of them upon exit.
449+
(Contributed by Yury Seliganov and others.)
450+
446451
datetime
447452
--------
448453

0 commit comments

Comments
 (0)