【问题标题】:Close a task in Python based on condition根据条件关闭 Python 中的任务
【发布时间】:2020-09-24 10:22:41
【问题描述】:

我正在使用 AsyncIO 和 Websockets 模块在 Python 中创建两个并发任务,每个任务都连接到一个 websocket 服务器并接收消息。

我正在尝试创建一个系统,当一个任务超过 4 秒没有收到消息时,它必须关闭连接和任务,但我很难弄清楚这一点。谁能帮我解决这个问题?

这是我尝试过的:

async def connect(URI):
    async with websockets.client.connect(URI) as ws:
        LastUpdate = time.time()
        while time.time() - LastUpdate < 4:
            LastUpdate = time.time()
            await ws.recv()

        print('Not receiving updates anymore')
                
async def main():
    Tasks = [asyncio.create_task(connect('URI1')), asyncio.create_task(connect('URI2'))]

    await asyncio.gather(*Tasks)

在这里,main 创建了两个并发任务并同时运行它们。 connect 负责连接和接收消息。我试图做的是在最后一次更新超过 4 秒之前打破 while 循环,但这不起作用,因为循环没有被打破。

【问题讨论】:

  • URI1 这一行有错字吗? async with websockets.client.connect(URI1) as ws:
  • 是的,已更正。谢谢!

标签: python python-3.x websocket python-asyncio


【解决方案1】:

你可以使用asyncio.wait_for:

async def connect(URI):
    async with websockets.client.connect(URI) as ws:
        while True:
            try:
                msg = await asyncio.wait_for(ws.recv(), 4)
            except asyncio.TimeoutError:
                break
            # do something with msg

        print('Not receiving updates anymore')

【讨论】:

  • 非常感谢您的回答!在这种情况下,我如何访问消息的内容?我尝试在您放置#do something with msg 的位置添加 print(await ws.recv()),但它给了我一个关于 wait_for 从未等待过的错误
  • 好的,所以要访问消息 ws.recv() 就可以了。至于错误,我认为问题在于它应该是 msg = await asyncio.wait_for(ws.recv(), 4)
  • @JayK23 内容本来是在局部变量msg 中,但你注意到了,我忘了在asyncio.wait_for() 前面放一个await。我现在修改了答案。
  • 这个很清楚。在我希望我的代码无限期地重试连接的情况下,在退出 while 循环后,我该怎么办?我目前使用更高级别的 while 循环来执行此操作,我将它放在 async def connect (..) 之后。它似乎有效,但我不知道是否有更好的方法来做到这一点。我知道这超出了问题,如果需要,我会创建另一个。再次非常感谢!
  • @JayK23 外循环(可能位于connect 的调用者中)是一个合理的解决方案,是的。
【解决方案2】:

有几种方法可以做到这一点。

  1. 您可以在异步例程中使用的包中设置超时。
  2. 您可以使用 asyncio 并在任务超时时终止它们。
  3. 您可以对所有操作使用非阻塞套接字,并且根本不使用 asyncio,而是使用 select() 或 poll()。这非常有效,但要复杂得多。

我会按照这个顺序进行,第一个在大多数情况下是最简单和最喜欢的。 Websockets 似乎可以处理超时:https://github.com/websocket-client/websocket-client/blob/29c15714ac9f5272e1adefc9c99b83420b409f63/websocket/_socket.py#L99

您可能想在套接字上设置超时。通常这是使用https://docs.python.org/3.8/library/socket.html#socket.socket.settimeout

我想 websockets 虽然在某个地方公开了这个功能,但我不熟悉它。

【讨论】:

  • 按照倒数第二段所暗示的那样设置套接字的超时将不起作用,因为 OP 使用的 websockets 不会暴露内部使用的套接字,还因为 socket.settimeout只影响阻塞套接字。 (实际上使用settimeout 甚至会使用make the socket blocking,所以它不能与asyncio 一起使用。)关于#3,使用非阻塞套接字和select/poll 正是asyncio 在内部所做的,所以asyncio 超时应该同样有效就这样。
  • @user4815162342 显然 #3 指的是不使用 asyncio 的情况。不过你是对的,我可能应该补充说,如果可能的话,应该通过 websockets 库来设置套接字超时。我不熟悉它,但我认为,由于您可以通过“socket”选项(github.com/websocket-client/websocket-client/blob/…)将套接字传递给 wc.connect(),因此应该手动完成。
  • 显然 #3 指的是不使用 asyncio 的情况 - 我明白,我只是指出没有理由使用这种方法比一开始只使用 asyncio 更有效,因为它最终会在内核级别做完全相同的事情。
  • 其实我觉得根本不是这样。尽管我已经有几年没有研究过 CPython 解释器了,但上次我检查时,从 Python 对单个套接字执行此操作的效率相当低,每次都在单个套接字上调用 select()。您滚动自己的非阻塞套接字代码并将它们批量处理以进行轮询/选择/等......总是会更有效率。这仅在您有数千个套接字时才有意义,并且编写这样的代码要困难得多,在这种特殊情况下也不是很有用。
  • 其实我不这么认为 - 然后你应该检查 asyncio 的来源并感到惊喜,因为它肯定会选择所有文件描述符 集体。这就是现代事件循环实现(如 asyncio)的全部意义所在,并且与本土解决方案一样高效(如果不是更高的话)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-22
  • 2019-09-22
  • 1970-01-01
  • 1970-01-01
  • 2022-01-20
相关资源
最近更新 更多