【问题标题】:Python asyncio force timeoutPython异步强制超时
【发布时间】:2015-04-20 23:37:11
【问题描述】:

使用 asyncio 可以在超时后执行协程,以便在超时后取消:

@asyncio.coroutine
def coro():
    yield from asyncio.sleep(10)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait_for(coro(), 5))

上述示例按预期工作(5 秒后超时)。

但是,当协程不使用asyncio.sleep()(或其他异步协程)时,它似乎不会超时。示例:

@asyncio.coroutine
def coro():
    import time
    time.sleep(10)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait_for(coro(), 1))

这需要超过 10 秒才能运行,因为 time.sleep(10) 没有被取消。这种情况下是否可以强制取消协程?

如果应该使用 asyncio 来解决这个问题,我该怎么做?

【问题讨论】:

    标签: python python-asyncio


    【解决方案1】:

    不,除非协程将控制权交还给事件循环,否则您不能中断协程,这意味着它需要在 yield from 调用中。 asyncio 是单线程的,因此当您在第二个示例中阻塞 time.sleep(10) 调用时,事件循环无法运行。这意味着当您使用wait_for 设置的超时到期时,事件循环将无法对其执行操作。直到coro 退出,事件循环才有机会再次运行,此时为时已晚。

    这就是为什么一般来说,您应该始终避免任何非异步的阻塞调用;任何时候调用阻塞而不屈服于事件循环,程序中的任何其他内容都无法执行,这可能不是您想要的。如果你真的需要做长时间的阻塞操作,你应该尝试使用BaseEventLoop.run_in_executor在线程或进程池中运行,这样可以避免阻塞事件循环:

    import asyncio
    import time
    from concurrent.futures import ProcessPoolExecutor
    
    @asyncio.coroutine
    def coro(loop):
        ex = ProcessPoolExecutor(2)
        yield from loop.run_in_executor(ex, time.sleep, 10)  # This can be interrupted.
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait_for(coro(loop), 1))
    

    【讨论】:

    【解决方案2】:

    感谢@dano 为您解答。如果运行coroutine 不是硬性要求,这里有一个重新设计的、更紧凑的版本

    import asyncio, time
    
    timeout = 0.5
    loop = asyncio.get_event_loop()
    future = asyncio.wait_for(loop.run_in_executor(None, time.sleep, 2), timeout)
    try:
        loop.run_until_complete(future)
        print('Thx for letting me sleep')
    except asyncio.exceptions.TimeoutError:
        print('I need more sleep !')
    

    出于好奇,在我的Python 3.8.2 中进行的小调试表明,将None 作为执行程序传递会导致创建_default_executor,如下所示:

    self._default_executor = concurrent.futures.ThreadPoolExecutor()
    

    【讨论】:

    • python进程在TimeoutError后继续运行。如果遇到 except 块,有没有办法让 python 程序退出?
    • @Justin 感谢您的评论,这让我更新了我对 python 3.8 的答案(except 中的不同类)。至于你的问题,让错误松动确实让解释器停止了我(要么完全删除except,要么在它的末尾删除raise
    【解决方案3】:

    我看到的超时处理示例非常简单。鉴于现实,我的应用程序有点复杂。顺序是:

    1. 当客户端连接到服务器时,让服务器创建另一个到内部服务器的连接
    2. 当内部服务器连接正常时,等待客户端发送数据。根据这些数据,我们可能会查询内部服务器。
    3. 当有数据要发送到内部服务器时,发送它。由于内部服务器有时响应不够快,请将此请求包装到超时中。
    4. 如果操作超时,折叠所有连接以向客户端发出错误信号

    为实现上述所有目的,在保持事件循环运行的同时,生成的代码包含以下代码:

    def connection_made(self, transport):
        self.client_lock_coro = self.client_lock.acquire()
        asyncio.ensure_future(self.client_lock_coro).add_done_callback(self._got_client_lock)
    
    def _got_client_lock(self, task):
        task.result() # True at this point, but call there will trigger any exceptions
        coro = self.loop.create_connection(lambda: ClientProtocol(self),
                                               self.connect_info[0], self.connect_info[1])
        asyncio.ensure_future(asyncio.wait_for(coro,
                                               self.client_connect_timeout
                                               )).add_done_callback(self.connected_server)
    
    def connected_server(self, task):
        transport, client_object = task.result()
        self.client_transport = transport
        self.client_lock.release()
    
    def data_received(self, data_in):
        asyncio.ensure_future(self.send_to_real_server(message, self.client_send_timeout))
    
    def send_to_real_server(self, message, timeout=5.0):
        yield from self.client_lock.acquire()
        asyncio.ensure_future(asyncio.wait_for(self._send_to_real_server(message),
                                                       timeout, loop=self.loop)
                                      ).add_done_callback(self.sent_to_real_server)
    
    @asyncio.coroutine
    def _send_to_real_server(self, message):
        self.client_transport.write(message)
    
    def sent_to_real_server(self, task):
        task.result()
        self.client_lock.release()
    

    【讨论】:

    • 这个答案似乎没有回答实际问题,我也不认为这有帮助。 (因此投反对票。)在代码中完成了太多不相关的事情,并且没有清楚地展示实际的超时处理。我希望这些反馈对您有所帮助。
    • 感谢您的反馈。实际问题是关于协程可以超时执行,我的代码就是这样做的。正如我在回答中所说,在整个 Internet 中找不到使用loop.run_until_complete() 以超时 执行协程的代码,这就是我发布此内容的原因。同样考虑到约束,方法/函数的数量似乎是强制性的。随时提供更优化的代码。
    猜你喜欢
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多