Python 3.x 与自定义超时实现的协同程序

Python 3.x 与自定义超时实现的协同程序,python-3.x,python-asyncio,Python 3.x,Python Asyncio,我正在使用asyncio运行一个子流程,它打印一些输出,我逐行解析这些输出,并根据我看到的输出执行各种操作。我想在此进程上设置一个超时,但它不应该是整个进程生命周期的全局超时。相反,每当我看到进程的某些特定输出时,我都希望实际重置超时,以便它重新开始。我如何实现这一点 对于全局超时,我可以很容易地使用它,我只需调用asyncio.wait\u For(\u foo(),timeout)。但我不能让它与重置超时一起工作。以下是我目前掌握的情况: # here invocation

我正在使用asyncio运行一个子流程,它打印一些输出,我逐行解析这些输出,并根据我看到的输出执行各种操作。我想在此进程上设置一个超时,但它不应该是整个进程生命周期的全局超时。相反,每当我看到进程的某些特定输出时,我都希望实际重置超时,以便它重新开始。我如何实现这一点

对于全局超时,我可以很容易地使用它,我只需调用
asyncio.wait\u For(\u foo(),timeout)
。但我不能让它与重置超时一起工作。以下是我目前掌握的情况:

        # here invocation is my own data structure with some bookkeeping information in it
        # (such as the start time from which I want to base my timeout decisions on).
        # and process is the value returned by await asyncio.create_subprocess_exec(...)
        # _run_one_invocation() is my own function which is just a stdout readline loop
        # and some dispatching.

        # Make a Task out of the co-routine so that we decide when it gets cancelled, not Python.
        run_task = asyncio.Task(_run_one_invocation(invocation, process))
        while True:
            try:
                # Use asyncio.shield so it doesn't get cancelled if the timeout expires
                await asyncio.shield(asyncio.wait_for(run_task, 1))

                # If the await returns without raising an exception, we got to EOF and we're done.
                break
            except asyncio.TimeoutError:
                # If it's been too long since last reset, this is a "real" timeout.
                duration = time.time() - invocation.start_time
                if duration > timeout:
                    run_task.cancel()
                    raise
当我运行此命令时,调用
run\u task.cancel()
的if语句没有被输入,但是当我返回循环顶部并再次调用
asyncio.wait\u for()
时,它会立即引发一个
asyncio.cancelederror


我做错了什么?

通过完全避免
wait\u for()
(因此
shield()
)和使用
wait(return\u when=FIRST\u COMPLETED)
来实现所需的超时,可以解决问题并简化代码:

run_task = asyncio.create_task(_run_one_invocation(invocation, process))
while True:
    await asyncio.wait([run_task], timeout=1)
    if run_task.done():
        break
    if time.time() - invocation.start_time > timeout:
        run_task.cancel()
        raise asyncio.TimeoutErrror()
这种方法的缺点是它引入1s唤醒,禁止程序(以及计算机)进入睡眠状态,即使任务休眠数小时。在服务器上可能没什么大不了的,但这样的做法会导致笔记本电脑的电池消耗,最好避免这样的做法。此外,1s睡眠引入了高达1s的延迟,以响应超时的变化

要解决此问题,您可以创建由更改超时的代码触发的事件,并在超时和任务完成之外对该事件做出反应:

timeout_changed = asyncio.Event()
# pass timeout_changed where needed, and have the code that changes
# the timeout also call timeout_changed.set()
run_task = asyncio.create_task(_run_one_invocation(invocation, process))
while True:
    remaining = timeout - (time.time() - invocation.start_time)
    timeout_changed_task = asyncio.ensure_future(timeout_changed.wait())
    await asyncio.wait([run_task, timeout_changed_task],
        return_when=asyncio.FIRST_COMPLETED, timeout=remaining)
    timeout_changed_task.cancel()
    # either: 1) the task has completed, 2) the previous timeout has
    # expired, or 3) the timeout has changed
    if run_task.done():
        break  # 1
    if time.time() - invocation.start_time > timeout:
        # 2 or 2+3
        run_task.cancel()
        raise asyncio.TimeoutErrror()
    # 3 - continue waiting with the new timeout

您必须屏蔽任务,而不是
wait_for
wait asyncio.wait_for(asyncio.shield(run_task),1)
请注意,很难看到此问题的有效答案是什么。您是在问如何实现移动超时吗?你是在问代码有什么问题吗?由于所有的
调用
过程
、以及
运行一次调用
基本上都是黑匣子,因此很难说出哪里出了问题以及如何做得更好。您可能希望它提供更多的关注点,并将代码扩展到一个更大的范围。@MisterMiyagi据我所知,OP想要实现一个移动超时。他还介绍了他试图实施该计划的情况,并指出了实施过程中存在的问题。虽然这个问题并非完美无缺(它没有提供一个最小且可重复的示例),但它似乎完全可以回答,不值得关闭。我不知道
asyncio.sleep()
。与使用
timeout=
关键字参数(asyncio.wait()接受)相比,这有什么好处?@ZacharyTurner除了正常的结果外,在不处理业务异常的情况下对代码进行推理似乎更容易。但是如果你认为它使代码更简单,你当然可以使用它。不,我同意它确实使代码更干净,而且我从不喜欢处理异常。只是想知道是否有技术上的原因,似乎没有。谢谢如果函数返回时任务处于挂起状态,我似乎需要取消
asyncio.sleep()
任务。否则,我会在关机时收到很多关于任务在挂起时被销毁的消息。这听起来对你正确吗?“这使代码变得有点难看。@ZacharyTurner的观点很好,这是因为我的代码没有经过测试。我想使用
timeout
参数
asyncio.wait()
会有好处。我现在检查了文档,并且
wait()
实际上根本不需要处理
TimeoutError
(它不会取消任何操作),因此使用
timeout
应该是一个简单的方法。我现在更新了答案,以便这样做。