任务组上的文档说:
特殊处理两个基本异常:如果任何任务因
或KeyboardInterrupt
失败,任务组仍取消剩余任务并等待它们,但随后会重新引发初始SystemExit
或KeyboardInterrupt
SystemExit
或ExceptionGroup
。BaseExceptionGroup
这让我相信,给出以下代码:
import asyncio
async def task():
await asyncio.sleep(10)
async def run() -> None:
try:
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(task())
t2 = tg.create_task(task())
print("Done")
except KeyboardInterrupt:
print("Stopped")
asyncio.run(run())
运行并按 Ctrl-C 应会打印
Stopped
;但事实上,异常没有被捕获:
^CTraceback (most recent call last):
File "<python>/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<python>/asyncio/base_events.py", line 685, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "<module>/__init__.py", line 8, in run
async with asyncio.TaskGroup() as tg:
File "<python>/asyncio/taskgroups.py", line 134, in __aexit__
raise propagate_cancellation_error
File "<python>/asyncio/taskgroups.py", line 110, in __aexit__
await self._on_completed_fut
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<frozen runpy>", line 189, in _run_module_as_main
File "<frozen runpy>", line 148, in _get_module_details
File "<frozen runpy>", line 112, in _get_module_details
File "<module>/__init__.py", line 15, in <module>
asyncio.run(run())
File "<python>/asyncio/runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "<python>/asyncio/runners.py", line 123, in run
raise KeyboardInterrupt()
KeyboardInterrupt
我错过了什么?正确的检测方法是什么
KeyboardInterrupt
?
这实际上不是
TaskGroup
的错。尝试运行这个:
>>> async def other_task():
... try:
... await asyncio.sleep(10)
... except KeyboardInterrupt:
... print("Stopped")
>>>
>>> asyncio.run(other_task())
KeyboardInterrupt
>>>
这也不会打印。也不是这个:
>>> async def other_task():
... try:
... await asyncio.sleep(10)
... except Exception as err:
... print("Stopped by", err)
>>>
>>> asyncio.run(other_task())
KeyboardInterrupt
>>>
你在这里抓不到
KeyboardInterrupt
。
asyncio.shield
是无用保护任务不被取消,它需要为asyncio编写我们自己的信号处理程序,因为任务取消会在内部触发KeyboardInterrupt
.
另一方面,
trio
- 由 Nathaniel J. Smith 第一个提出现代结构化并发的人 - 清楚地实现了您的意图。
>>> import trio
>>>
>>> async def task():
... await trio.sleep(10)
>>>
>>> async def run():
... try:
... async with trio.open_nursery() as nursery:
... for _ in range(10):
... nursery.start_soon(task)
... except KeyboardInterrupt:
... print("Stopped")
...
... trio.run(run)
Stopped
>>>
与 asyncio 不同,这不是回调汤,使其更加稳定、直观、可预测。 (并不是说 asyncio 是垃圾;它在创建时就是正确的选择。)