【问题标题】:Timeout for python coroutinespython协程超时
【发布时间】:2017-06-15 09:37:19
【问题描述】:

如何让协程超时停止?

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

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

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

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

Here is my code

【问题讨论】:

  • 我认为它与“Python asyncio force timeout”这个问题不同,因为我没有使用像 time.sleep(n) 这样的阻塞语句。
  • 正如评论所示,这确实与其他问题完全不同,问题在于在异步函数中使用阻塞调用。详情见我的回答。
  • 嗨,这是很久以前的事了,还是谢谢你:)
  • 没问题,在我对 asyncio 感兴趣之前,你问了你的问题。我偶然发现了它,想着一个真正有趣的问题被错误地标记为重复是多么可悲——但有时 StackOverflow 就是这样。很高兴能帮上忙。
  • 我还没有学好 asyncio :) 可能需要每天都使用它,记住...)

标签: python python-asyncio coroutine python-sockets


【解决方案1】:

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

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

if data == b'':
    break

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

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

例如,以下协程完全阻塞了事件循环并阻止任何其他协程运行,尽管它的内部循环在等待:

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

async def noop():
    pass

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

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

【讨论】:

  • 我已经忘记了 asyncio 了。您能否简单地告诉我,该对象如何“表明它没有准备好值”?或者它是如何表示相反的?
  • @ЕвгенийАртеменко 如果是sock_recv,它会继续尝试从非阻塞套接字读取。如果它得到一些数据,则意味着套接字是可读的(“相反”),并且数据被返回。如果读取返回 -1/EWOULDBLOCK,则协程在将描述符注册到事件循环后立即挂起,以便在套接字变得可读时将其唤醒。请参阅this video,了解如何实现这样的系统。 (Asyncio 的工作方式略有不同,但基本思想是相同的。)
  • 我想知道 asyncio 使用什么来表示“指示”,但还是谢谢 :)
  • @ЕвгенийАртеменко 链接的视频实际上也涵盖了该部分。在 asyncio 中,the code 等待新创建的 Future;因为它没有与之关联的值,所以等待它会自动挂起协程。 (未来通过拥有一个 __await__ 来实现这一点,其实现使用产生的生成器。)
  • 使用基于生成器的协程,生成器屈服表示暂停,生成器返回(引发StopIteration)表示完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-18
  • 2020-02-23
  • 1970-01-01
  • 2017-07-26
  • 2020-08-28
  • 2020-01-07
  • 2011-04-13
相关资源
最近更新 更多