可以轻松且安全地取消任务。当取消任务时,
asyncio.CancelledError
will be raised in the task at the next opportunity.
It is recommended that coroutines use
try/finally
blocks to robustly perform clean-up logic. In case
asyncio.CancelledError
is explicitly caught, it should generally be propagated when clean-up is complete.
asyncio.CancelledError
directly subclasses
BaseException
so most code will not need to be aware of it.
The asyncio components that enable structured concurrency, like
asyncio.TaskGroup
and
asyncio.timeout()
, are implemented using cancellation internally and might misbehave if a coroutine swallows
asyncio.CancelledError
. Similarly, user code should not generally call
uncancel
. However, in cases when suppressing
asyncio.CancelledError
is truly desired, it is necessary to also call
uncancel()
to completely remove the cancellation state.
Task groups combine a task creation API with a convenient and reliable way to wait for all tasks in the group to finish.
-
class
asyncio.
TaskGroup
¶
-
An
异步上下文管理器
holding a group of tasks. Tasks can be added to the group using
create_task()
. All tasks are awaited when the context manager exits.
Added in version 3.11.
-
create_task
(
coro
,
*
,
名称
=
None
,
context
=
None
)
¶
-
Create a task in this task group. The signature matches that of
asyncio.create_task()
. If the task group is inactive (e.g. not yet entered, already finished, or in the process of shutting down), we will close the given
coro
.
Changed in version 3.13:
Close the given coroutine if the task group is not active.
范例:
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(some_coro(...))
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). 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
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.,
__aexit__()
hasn’t been called yet), the task directly containing the
async with
statement is also cancelled. The resulting
asyncio.CancelledError
will interrupt an
await
, but it will not bubble out of the containing
async with
语句。
Once all tasks have finished, if any tasks have failed with an exception other than
asyncio.CancelledError
, those exceptions are combined in an
ExceptionGroup
or
BaseExceptionGroup
(as appropriate; see their documentation) which is then raised.
Two base exceptions are treated specially: If any task fails with
KeyboardInterrupt
or
SystemExit
, the task group still cancels the remaining tasks and waits for them, but then the initial
KeyboardInterrupt
or
SystemExit
is re-raised instead of
ExceptionGroup
or
BaseExceptionGroup
.
If the body of the
async with
statement exits with an exception (so
__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
__aexit__()
, unless it is
asyncio.CancelledError
, is also included in the exception group. The same special case is made for
KeyboardInterrupt
and
SystemExit
as in the previous paragraph.
Task groups are careful not to mix up the internal cancellation used to “wake up” their
__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.
In the case where a task group is cancelled externally and also must raise an
ExceptionGroup
, it will call the parent task’s
cancel()
method. This ensures that a
asyncio.CancelledError
will be raised at the next
await
, so the cancellation is not lost.
Task groups preserve the cancellation count reported by
asyncio.Task.cancelling()
.
Changed in version 3.13:
Improved handling of simultaneous internal and external cancellations and correct preservation of cancellation counts.
Terminating a Task Group
¶
While terminating a task group is not natively supported by the standard library, termination can be achieved by adding an exception-raising task to the task group and ignoring the raised exception:
import asyncio
from asyncio import TaskGroup
class TerminateTaskGroup(Exception):
"""Exception raised to terminate a task group."""
async def force_terminate_task_group():
"""Used to force termination of a task group."""
raise TerminateTaskGroup()
async def job(task_id, sleep_time):
print(f'Task {task_id}: start')
await asyncio.sleep(sleep_time)
print(f'Task {task_id}: done')
async def main():
try:
async with TaskGroup() as group:
# spawn some tasks
group.create_task(job(1, 0.5))
group.create_task(job(2, 1.5))
# sleep for 1 second
await asyncio.sleep(1)
# add an exception-raising task to force the group to terminate
group.create_task(force_terminate_task_group())
except* TerminateTaskGroup:
pass
asyncio.run(main())
期望输出:
Task 1: start
Task 2: start
Task 1: done