【发布时间】:2021-06-15 15:28:28
【问题描述】:
问题应该很简单,但我找不到任何相关信息。
我有一个异步 python 程序,其中包含一个运行时间相当长的任务,我希望能够在任意点暂停和重新启动(任意点当然意味着任何有 await 关键字的地方)。
我希望有一些类似于task.suspend() 和task.resume() 的东西,但似乎没有。
在任务或事件循环级别上是否有任何 API 或我需要以某种方式自己执行此操作?我不想在每次等待之前放置event.wait()...
谢谢
【问题讨论】:
-
我认为需要明确的
sleep(0)可能表明我的实现处理取消的方式存在缺陷。 (sleep(0)几乎总是 asyncio 代码中的“代码气味”。)也许您需要在内部while循环周围使用 try/except CancallError,如果是CancelledError,请执行send, message = iter_throw, exception_instance。这样,中断Event.wait的取消将正确传播到协程。 -
嗯,我认为你的实现很好。我使用您的代码从 asyncio-docs 中制作了一个用于取消任务的最小示例,并且在不使用
asyncio.sleep(0)的情况下一切都按预期工作。然而,在我第一次尝试最小的例子时,我错误地将await挂起,导致RuntimeError,因为它已经在run_wrapper中被awaited。我在实际应用程序中也是这样做的,所以我猜测RuntimeError可能已被 uvicorn 吞噬但导致意外行为。 -
对,等待suspendable是不允许的,因为它的所有权被
run_wrapper接管,而run_wrapper又被任务拥有。run_wrapper是唯一需要的,因为create_task()AFAIR 需要一个实际的协程。也许我可以将suspendable直接传递给ensure_future(),但我不想做实验,代码已经足够参与了。 -
你担心是对的 :)。我用最小的示例重复了测试,但我忽略了虽然任务在暂停时被取消,但
CancelledError并未在 coro 内引发。该异常实际上是在 yield from 时引发的,并且可以按照您的建议被另一个 try/except 捕获。我将再次更新上面的代码以反映这些更改。通过这个实现,我能够取消任务而无需任何额外的asyncio.sleep(0),无论是否暂停。 -
问题是当一个暂停的任务被取消时你想要发生什么。我的实施非常重视暂停,并在取消之前等待恢复。 (我不确定在您的使用中如何发生死锁。)如果这是您需要的语义,我认为可以按照您的方式更改代码。我可能将循环条件写为
while send is not iter_throw and not self._can_run.is_set(),但这等同于您在 asyncio 中的表述,因为事件循环将通过None消息或通过提供CancelledError异常来恢复我们。
标签: task python-asyncio event-loop suspend