我有一个等待对象,用于实现请求/答复事务。如果交易超时,在放弃并引发异常之前将重试几次。
现在假设它总是超时,因为那是我遇到的问题。
当任务开始此操作然后被取消时,重试将继续。这不是我想要的。我想完全取消操作。
我准备了一个MCVE,并注意到,取消任务时,该任务正在等待的未来会被取消。这很适合我,它可能是解决方案的基础,但是我不明白为什么取消这种未来以及我是否可以依靠它。
import asyncio
RETRIES = 2
TIMEOUT = 1.0
class ClientRPC:
def __init__(self):
self._reply = None
self._retries = RETRIES
def __await__(self):
self.start()
return self._reply.__await__()
def start(self):
loop = asyncio.get_event_loop()
if self._reply is None:
self._reply = loop.create_future()
loop.call_later(TIMEOUT, self.handle_timeout)
# send a request
print("REQUEST")
def handle_timeout(self):
print("TIMEOUT")
print("future", repr(self._reply._state))
if self._retries > 0:
self._retries -= 1
self.start()
else:
self._reply.set_exception(RuntimeError("Timeout!"))
def handle_reply(self, reply):
# unused in this example
pass
async def client():
transaction = ClientRPC()
try:
reply = await transaction
except asyncio.CancelledError:
print("--CANCELLED--")
async def test():
loop = asyncio.get_event_loop()
task = loop.create_task(client())
await asyncio.sleep(1.5)
task.cancel()
await asyncio.sleep(3)
asyncio.run(test()) # python 3.7+
输出(省略回溯):
REQUEST TIMEOUT future 'PENDING' REQUEST --CANCELLED-- TIMEOUT future 'CANCELLED' <-- why? REQUEST TIMEOUT future 'CANCELLED' Exception in callback ClientRPC.handle_timeout() handle: asyncio.base_futures.InvalidStateError: invalid state
答案 0 :(得分:2)
我准备了一个MCVE,并注意到,取消任务时,该任务正在等待的未来会被取消。这很适合我,它可能是解决方案的基础,但是我不明白为什么取消这种未来以及我是否可以依靠它。
是的,如果任务等待一个未来,那么该未来将被取消。那个未来可能是另一项任务,因此取消将扩展到等待的最底层的未来。的实现makes sure,但文档并未明确说明。
出于两个原因,我将继续依赖此行为:
如果不对后向兼容性造成重大破坏,就无法在此时进行更改。开发人员已经拒绝了smaller更改,因为它们会破坏现有代码。
没有其他其他方法可以实现这一目标,而不会导致资源泄漏。如果要取消的任务正在等待将来,除了取消该任务外,您该怎么办?如果只是让它在后台运行,则可能会使它永远存在,因为未来可能永远不会独立存在。如果只是通过将其从调度程序中删除(同样又没有取消)来“修复”,那么未来将永远没有机会清理它获取的资源,这肯定会导致资源泄漏。
因此,可以安全地依靠取消向下传播,但用asyncio.shield()
保护的期货除外,该期货保留给 meant 以便在后台运行的期货,有自己的终身管理。