我想实现两个协程函数,其中一个将值传递给另一个。返回或异常用于表示传输是否成功。在任何情况下,如何确保两个协约人始终就转让结果达成一致?
下面是部分可行的实现。
async def prevent_return_leak(f):
assert isinstance(f, asyncio.Future)
try:
return await f
except asyncio.CancelledError:
if f.cancelled():
raise
asyncio.current_task().cancel() # delay to next yield
return f.result()
class Exchange:
'''
Transfer a value. There can be at most one unfinished `give`
call at any time. The same applies to `take`.
'''
def __init__(self):
self.taker = None
self.giver = None
self.value = None
async def take(self):
'''
Wait until `give(value)` is called, then return `value`.
If this function returns, the `give` call from which `value`
is taken is guaranteed to return.
'''
if self.giver is not None:
if not self.giver.done():
self.giver.set_result(None)
return self.value
f = asyncio.Future()
assert self.taker is None
self.taker = f
try:
return await prevent_return_leak(f)
finally:
assert self.taker is f
self.taker = None
async def give(self, value):
'''
Wait until `value` is taken by a `take` call. If this function
returns, exactly one `take` call is guaranteed to return with
`value`.
'''
if self.taker is not None:
if not self.taker.done():
self.taker.set_result(value)
return
f = asyncio.Future()
assert self.giver is None
self.giver = f
try:
return await prevent_return_leak(f)
finally:
assert self.giver is f
self.giver = None
一个简单的测试用例:
async def take(e):
print(await e.take())
print('take done')
async def test():
e = Exchange()
t1 = asyncio.create_task(take(e))
await asyncio.sleep(0) # run event loop once
t2 = asyncio.create_task(e.give(1))
await asyncio.sleep(0) # run event loop once
print(t1)
print(t2)
t1.cancel()
print(await t1)
print('test done')
loop.run_until_complete(test())
# <Task pending coro=<take() running at <ipython-input-17-63737f0439e2>:2> wait_for=<Future finished result=None>>
# <Task finished coro=<Exchange.give() done, defined at <ipython-input-3-90b4b3b6a3d3>:40> result=None>
# value
# take done
# ---------------------------------------------------------------------------
# CancelledError Traceback (most recent call last)
如您所见,take()
正常完成,这是预期的。但是,test()
被取消并且当take()
返回到test()
时,该值会丢失。这是由于asyncio.Task
将自身标记为已取消,如果包装的协程刚好在返回之前请求自取消。
我事件试图修补asyncio.Task
,但仅适用于纯Python版本,即asyncio.tasks._PyTask
。 asyncio
甚至可能吗?