【问题标题】:Python asyncio program won't exitPython asyncio 程序不会退出
【发布时间】:2016-09-14 00:34:38
【问题描述】:

我有一个带有两个异步任务的 asyncio/Python 程序:

  • 一个崩溃的
  • 永远持续下去。

我希望我的整个程序在第一次崩溃后退出。我不能让它发生。

import asyncio
import time

def infinite_while():
    while True:
        time.sleep(1)


async def task_1():
    await asyncio.sleep(1)
    assert False


async def task_2():
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, lambda: infinite_while())


loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)

tasks = asyncio.gather(task_2(), task_1())
try:
    loop.run_until_complete(tasks)
except (Exception, KeyboardInterrupt) as e:
    print('ERROR', str(e))
    exit()

它打印错误但不退出。手动关闭时,程序会打印以下堆栈跟踪:

Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 39, in _python_exit
    t.join()
  File "/usr/lib/python3.5/threading.py", line 1054, in join
    self._wait_for_tstate_lock()
  File "/usr/lib/python3.5/threading.py", line 1070, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt

【问题讨论】:

  • 您尝试过合作退出吗?可能不是您想要的,但至少作为一个实验,您可以:将infinite_while 中的循环更改为while not exit_requested;更改task_1 以捕获断言异常,设置标志并重新引发;看看你对 exit() 的调用是否在每个任务退出后完成,一个是正常的,一个是断言异常的。
  • 谢谢,它有效,将使用它作为最后的手段,但希望有一种更清洁的方法来解决它。

标签: python python-asyncio


【解决方案1】:

当任务中出现异常时,它永远不会传播到通过事件循环启动任务的范围,即loop.run_until_complete(tasks) 调用。想一想,好像异常只在你的任务上下文中抛出,那是你有机会处理它的*范围,否则它将在“背景”中上升>.

这就是说,你永远不会从这个任务中捕获Exception

try:
    loop.run_until_complete(tasks)
except (Exception, KeyboardInterrupt) as e:
    print('ERROR', str(e))
    exit()

...这就是事件循环的工作方式。想象一下,如果您有一个包含多个任务的服务,其中一个会失败,那么整个服务就会停止。

当您在task1 中捕获异常时,您可以手动stop 事件循环,例如

async def task_1():
    await asyncio.sleep(1)
    try:
        assert False
    except Exception:
        # get the eventloop reference somehow
        eventloop.stop()

但是,这很脏而且有点hacky,所以我宁愿建议使用@D-Von suggested 的解决方案,它更清洁、更安全。

【讨论】:

  • 我有一个我想要的用例。事件循环在我需要并发的一小部分代码中启动和停止。如果发生异常,我希望整个事件循环停止并且程序退出。在事件循环中,我有几个长时间运行但不是无限运行的进程,如果发生一个异常,我想将它们全部取消。我的看法是,asyncio 是为事件循环始终运行的世界而设计的,但它也是一个适用于不同用例的好库——如果异常处理更加透明的话。
  • “想象一下,如果你有一个包含多个任务的服务,其中一个会失败,那么整个服务就会停止。”我认为完全停止服务几乎总是期望的行为,因为一项任务崩溃后的行为可能是未定义和未经测试的。