asyncio.TaskGroup 中的键盘中断

问题描述 投票:0回答:1

任务组上的文档说:

特殊处理两个基本异常:如果任何任务因

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

python python-3.x exception python-asyncio keyboardinterrupt
1个回答
0
投票

这实际上不是

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 是垃圾;它在创建时就是正确的选择。)

© www.soinside.com 2019 - 2024. All rights reserved.