python协程超时

时间:2017-01-30 09:19:53

标签: python python-asyncio coroutine python-sockets

如何在超时后停止协程?

我不明白为什么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 

正如我想到的那样,在某些时候(在await语句中)将控制权返回给事件循环。我认为应该在没有更多数据要接收时发生。 如果事件循环具有控制权,则可以在超时时停止。

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

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

Here is my code

1 个答案:

答案 0 :(得分:3)

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

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

if data == b'':
    break

...在data = await loop.sock_recv(...)行之后。

但上述内容仍无法解释为什么wait_for无法取消流氓协程。问题是await并不意味着将控制传递给事件循环&#34;,因为它有时被描述。它意味着从提供的等待对象请求值,产生对事件循环的控制如果对象指示它没有准备好的值。&#34; if 至关重要:如果对象 的值已准备就绪,则此值将立即使用,而不会推迟到事件循环。换句话说,await 并不能保证事件循环有机会运行。

例如,以下协同程序完全阻止事件循环并阻止任何其他协同程序运行,尽管其内部循环由等待组成:

async def busy_loop():
    while True:
        await noop()

async def noop():
    pass

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

为了保证其他任务有机会运行,您可以在循环中添加await asyncio.sleep(0)。这对于大多数代码来说都不是必需的,在这些代码中,请求IO数据很快就会导致等待,此时事件循环将启动。它只与EOF处理错误相结合,导致代码卡住。