@@ -103,6 +103,29 @@ To actually run a coroutine, asyncio provides three main mechanisms:
103
103
world
104
104
finished at 17:14:34
105
105
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
+
106
129
107
130
.. _asyncio-awaitables :
108
131
@@ -223,6 +246,11 @@ Creating Tasks
223
246
:exc: `RuntimeError ` is raised if there is no running loop in
224
247
current thread.
225
248
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
+
226
254
.. important ::
227
255
228
256
Save a reference to the result of this function, to avoid
@@ -254,6 +282,76 @@ Creating Tasks
254
282
Added the *context * parameter.
255
283
256
284
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
+
257
355
Sleeping
258
356
========
259
357
@@ -327,8 +425,9 @@ Running Tasks Concurrently
327
425
cancellation of one submitted Task/Future to cause other
328
426
Tasks/Futures to be cancelled.
329
427
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 `.
332
431
333
432
.. _asyncio_example_gather :
334
433
0 commit comments