@@ -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+
257355Sleeping
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
0 commit comments