【问题标题】:How to easily find a coroutine that has timed out?如何轻松找到已超时的协程?
【发布时间】:2019-05-16 01:42:26
【问题描述】:

关键问题:asyncio.wait(aws,timeout=1,return_when=FIRST_COMPLETED) 有没有简单的方法可以检查返回的任务是否超时?

这是一个扩展问题。

场景是这样的:

  • 协程总数未知
  • 服务器只允许 10 个链接
  • 服务器会返回一个看似正确的结果(例如返回一个不正确的页面)
  • 服务器有时不返回任何数据。
  • 最大可能访问所有数据

所以为了更快的获取数据,我需要限制协程的数量。检查返回的页面。并超时。

目前有两种简单的方法。
1.类似线程,使用queue搭建协程池+10个无限循环coro。我真的不喜欢它。事实上,这种方法效果非常好。
2.我尝试使用async python3.7的高级API,尽量简化程序结构,使用while tasks&asyncio.wait&return_when

在这里,我遇到了如何查找协程超时的问题。

我构建了一个简单的演示:

import asyncio


async def test(delaytime):
    print(f"begin {delaytime}")
    await asyncio.sleep(delaytime )
    print(f"finish {delaytime} ")

async def main():
    # the number of tasks is unknow,range(10) is just a demo
    allts = list(range(10))
    ts = []
    while len(ts)<5:
        arg = allts.pop()
        t = asyncio.create_task(test(arg))
        t.arg = arg
        ts.append(t)
    while ts:
        dones,pendings = await asyncio.wait(ts,timeout=2,return_when=asyncio.FIRST_COMPLETED)
        for t in dones:
            # if check t.result() is error , i can append ts again
            print(t.arg,"is done")
            ts.remove(t)
            while len(ts)<5:
                if len(allts):
                    arg = allts.pop()
                    t = asyncio.create_task(test(arg))
                    t.arg = arg
                    ts.append(t)
                else:
                    break
        # for t in pendings:
        #   # if can check t is timeout , i can append ts again
        #   pass

if __name__=="__main__":
    asyncio.run(main())

调试后知道return_when=asyncio.FIRST_COMPLETEDasyncio.wait返回的任务都在pending中,除了已完成的任务。
但是,我不知道哪个任务超时。 我考虑过使用wait_for,但wait_for 没有return_when 参数。

有没有简单的方法来确定超时任务以便重新加入ts

【问题讨论】:

  • 因为它是异步的,所以逻辑是要么发出信号,要么留下痕迹。最简单的方法可能是写入日志文件,您应该一直这样做。
  • 尝试检查t.exception()。此外,它是“待定”,而不是“待定”。
  • @user4815162342 t.exception() 无效,所有待处理的任务都显示Exception is not set.,实际上是一个asyncio.InvalidStateError。无法判断待处理的任务是否已超时。抱歉,我的拼写错误。
  • 答案中的方法是否适用于您的情况?
  • @user4815162342 你的回答很有用,谢谢!尽管我仍然认为这种用法很丑陋。但是仅仅使用 asyncio 的高级 API,这可能是最好的编写方式。除了上面提到的queuewait(FIRST_COMPLETED),这个场景还有其他pythonic的想法吗?

标签: python python-asyncio


【解决方案1】:

问题是使用wait(return_when=FIRST_COMPLETED) 的方法与timeout 的使用根本不兼容。由于不同的任务在不同的时间开始,单个timeout 参数显然不能适用于所有任务。如果要使用return_when=FIRST_COMPLETED,请将每个任务包装在asyncio.wait_for中:

t = asyncio.create_task(asyncio.wait_for(test(arg), 2))

然后,当任务完成后,您可以使用t.exception() 来测试它是否已经超时,在这种情况下它会返回asyncio.TimeoutError。此检查应仅在 done 任务中执行。

【讨论】:

  • 我看到asyncio.wait的源代码,它只使用waiter而不是在每个task后附加一个waiter来确定超时。当FIRST_COMPLETED时,服务员清零。因此,无法识别后端每个任务的超时时间。我误解了wait_for的用法,还以为是wait之类的。我试过as_completed,但是他没有返回任务,并且无法附加参数进行重试,不符合场景要求。
  • 你的回答彻底解决了wait(FIRST_COMPLETED)的问题,谢谢!个人觉得task对象缺少初始参数属性(这样就得给任务对象附加动态属性重试),wait(FIRST_COMPLETED)的timeout属性比较别扭。只是我这么认为吗?
  • @notback 我认为没有真正的问题,wait(return_when=FIRST_COMPLETED) 根本不适合这项工作。就我个人而言,我会采用队列和固定数量的工作人员的解决方案,在工作人员内部使用asyncio.wait_for 来强制超时。
  • @user4815162342 queue 解决办法,对于超时,连接限制,更方便。但是对于返回看似正确的结果,以及各种重试操作,代码行数要大得多。于是我尝试了wait(FIRST_COMPLETED)的解决方案。顺便问一下,wait(FIRST_COMPLETED) 的更好情况是什么?
猜你喜欢
  • 2018-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-13
  • 1970-01-01
  • 2018-11-16
  • 2011-10-05
  • 1970-01-01
相关资源
最近更新 更多