python 协程超时

Евг*_*нко 2 python coroutine python-sockets python-asyncio

如何让协程因超时而停止?

我不明白为什么 asyncio.wait_for() 对我不起作用。我有这样一段代码(计划实现 telnet 客户端):

def expect(self, pattern, timeout=20): 
    if type(pattern) == str:
        pattern = pattern.encode('ascii', 'ignore')        
    return self.loop.run_until_complete(asyncio.wait_for(self.asyncxpect(pattern), timeout))

async def asyncxpect(self, pattern): #receives data in a cumulative way until match is found
    regexp = re.compile(b'(?P<payload>[\s\S]*)(?P<pattern>%s)' %pattern)
    self.buffer = b''
    while True:
        # add timeout
        # add exception handling for unexpectedly closed connections
        data = await self.loop.sock_recv(self.sock, 10000) 
        self.buffer += data
        m = re.match(regexp, self.buffer)
        if m:
            payload = m.group('payload')
            match = m.group('pattern')
            return payload, match 
Run Code Online (Sandbox Code Playgroud)

正如我所认为的,这段代码在某个时刻(在等待语句中)将控制权返回给事件循环。我认为当没有更多数据可以接收时应该发生这种情况。如果事件循环有控制权,它可以超时停止。

但是,如果服务器没有发送任何有用的(匹配的)我的代码就会在这个循环中绊倒,就在等待点。

我认为这与这个问题Python asyncio force timeout不同,因为我没有使用像 time.sleep(n) 这样的阻塞语句。

这是我的代码

use*_*342 5

当服务器关闭连接时,sock_recv返回一个空字节数组(b''),表示文件结束。由于您不处理该情况,因此您的代码最终会陷入处理同一缓冲区的无限循环中。

要更正它,请添加以下内容:

if data == b'':
    break
Run Code Online (Sandbox Code Playgroud)

...data = await loop.sock_recv(...)线路之后。

但上面还是没有解释为什么wait_for无法取消流氓协程。问题是,这await并不意味着“将控制权传递给事件循环”,正如有时所理解的那样。它的意思是“从提供的可等待对象请求值,如果(并且只要)该对象指示它没有准备好值,则将控制权交给事件循环。” if至关重要:如果对象在第一次询问时确实已经准备好值,则将立即使用该值,而不会推迟事件循环。换句话说,await 并不能保证事件循环有机会运行。

例如,以下协程完全阻止事件循环并阻止任何其他协程运行,尽管其内部循环只包含等待

if data == b'':
    break
Run Code Online (Sandbox Code Playgroud)

在您的示例中,由于套接字在文件末尾时根本不会阻塞,因此协程永远不会挂起,并且(与上述错误相结合)您的协程永远不会退出。

为了确保其他任务有机会运行,您可以添加await asyncio.sleep(0)循环。对于大多数代码来说这不是必需的,因为请求 IO 数据很快就会导致等待,此时事件循环将启动。(事实上,需要这样做通常表明存在设计缺陷。)在这种情况下,它是只有与 EOF 处理错误结合在一起,代码才会被卡住。