如何创建一对全部成功或全部失败的协程?

时间:2018-12-18 10:58:46

标签: python python-asyncio

我想实现两个协程函数,其中一个将值传递给另一个。返回或异常用于表示传输是否成功。在任何情况下,如何确保两个协约人始终就转让结果达成一致?

下面是部分可行的实现。

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._PyTaskasyncio甚至可能吗?

0 个答案:

没有答案