【问题标题】:python asyncio explanations and signals handlerpython asyncio 解释和信号处理程序
【发布时间】:2020-06-26 18:30:45
【问题描述】:

我有这段代码来测试 asyncio 是如何工作的:

stop = False


async def subcoro():
    # same result even with a range of 30
    for i in range(30000):
        pass

async def first():
    global stop
    while not stop:
        await subcoro()
        # without sleep no signal is triggered
        await asyncio.sleep(0.1)


async def main(loop):
    coro = asyncio.ensure_future(first())
    await asyncio.wait([coro], loop=loop)


def end():
    global stop
    stop = True


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    for signame in ('SIGINT', 'SIGTERM'):
        loop.add_signal_handler(getattr(signal, signame), end)
    try:
        loop.run_until_complete(asyncio.ensure_future(main(loop)))
    finally:
        loop.close()
        print('Bye!')

为什么我必须让first() 休眠一段时间(甚至小于 0.1)才能让程序处理信号? 是否有其他方式可以优雅地关闭所有协程和事件循环?

更新: 在 python-forum.org 上,他们告诉我阅读 PEP-0492,但它没有提供任何解决方案或建议。

更新2: 我的真实应用:https://github.com/FedericoTorsello/Embedded/tree/serialIO

【问题讨论】:

  • 您能否在您的问题中为我们提供一个最小、完整且可验证的问题示例,好吗? stackoverflow.com/help/mcve
  • 对不起,但链接中的应用程序非常“最小”(连接到 arduino,设置阅读器线程并运行协程)。我发现问题在于调用另一个线程中的函数,该线程从串行中读取数据,而在异步循环中处理得不好。我会尝试切换到实验性的 serial.aio 来弄清楚如何将函数 write_line 包装到协程中。

标签: python python-3.x async-await python-asyncio


【解决方案1】:

我喜欢这个问题,我首先尝试的是straceboth:

对于sleep,我看到很多epoll(等待通过套接字“转发”的信号,通常是为了符合处理接收信号所需的原子性)。

没有sleep就没有epoll,所以进程接收到信号,将信息推送到sochet上,但从不读取。

这是为什么呢?从语义的角度来看,:

while not stop:
    await subcoro()

的意义上是“牢不可破的”

await 类似于yield from,暂停执行 [the] coroutine 直到 [the] awaitable 完成并返回结果数据。

但是由于您的subcoro 没有将手还给循环,它将“立即”返回,因此满足“等待”并且循环再次循环,永远不会给主循环赶上的机会.

所以你真的处于“无限的while循环”中,永远不会把手交给主循环。

现在,使用asyncio.sleep,您将手交还给主循环,显然,asyncio.sleep 的实现是这样做的,因此主循环可以在睡眠期间执行其他操作,例如检查网络事件,例如在套接字上接收到的信号。还有另一种方法可以明确地将手还给循环,是一个空的“yield”,例如:

@asyncio.coroutine
def cooperate():
    yield
    return

现在调用await cooperate() 与不实际休眠的await asyncio.sleep(0.1) 具有相同的效果,这是asyncio.sleep 在延迟0 时所做的:

@coroutine
def sleep(delay, result=None, *, loop=None):
    """Coroutine that completes after a given time (in seconds)."""
    if delay == 0:
        yield
        return result
    [...]

从实现的角度来看,现在:

随着睡眠,asyncio.base_events.BaseEventLoop._run_once被重复调用,但没有sleep就永远不会返回,可能是因为你的first永远不会返回它,我没有深入检查它。

总结: subcoro 没有任何意义,在现实世界的应用程序中,它会为主循环提供一些时间,通常是通过调用网络或等待任何东西。

【讨论】:

  • 嗨!在我的真实应用程序中,我的 subcoro 实际上使用 aiohttp 将帖子数据发送到服务器,而我的 first 从消息队列中读取并调用 subcoro(如果需要将消息发送到服务器,则打印它) .看着 sleep 实现我虽然你同样的事情,但我不明白为什么我必须用它来停止我的循环执行:我的意思是......它根本不是 pythonic。
  • 如果您的 subcoro await 的网络请求由 aiohttp 完成,但您仍然没有收到您的信号,请与我们分享一个示例 :-)
  • 能否请您提供代码形式的Q的解决方案。不是方式,而是我们需要的解决方案。
  • @qrtLs 您需要的解决方案是了解,不要从堆栈溢出中复制/粘贴。
【解决方案2】:

由于 loop.add.. 调用函数/处理程序的 sig 可以在信号处理期间与循环交互(无需显式传递它或其他参数,如看到的 doc)。在 lib aiohttp/gunicorn 之后,例如,在所有 coros except CustmException: 子句中引发 CustomException() 和清理。示例:

import asyncio # etc

class GracefulExit(SystemExit):
    def __init__(self, msg=None, code=None):
        super(GracefulExit, self).__init__(msg)
        self.code = code


def handle_SIGTERM_exit(signum):
    logging.debug(f'handle {signal.Signals(signum)!r}')
    raise GracefulExit(code=0) # https://docs.python.org/3/library/asyncio-eventloop.html#set-signal-handlers-for-sigint-and-sigterm


def handle_SIGABRT_exit(signum): # else Fatal Python error: Aborted
    raise GracefulExit(code=1)

async def main():
    loop = asyncio.get_event_loop()
    loop.add_signal_handler(signal.SIGTERM, functools.partial(handle_SIGTERM_exit, signal.SIGTERM)) 
    loop.add_signal_handler(signal.SIGABRT, handle_SIGABRT_exit, signal.SIGABRT, None)
    # add other coros to loop (that catch or finally:) 

asyncio.run(main())

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-09
    • 2014-06-12
    • 1970-01-01
    • 1970-01-01
    • 2022-12-23
    • 2012-02-19
    • 2013-01-13
    • 1970-01-01
    相关资源
    最近更新 更多