首页 文章

future.add_done_callback()的用例是什么?

提问于
浏览
1

我理解如何向未来添加回调方法,并在未来完成时调用它 . 但是,当你已经可以从协程内部调用函数时,为什么这会有用呢?

回调版本:

def bar(future):
    # do stuff using future.result()
    ...

async def foo(future):
    await asyncio.sleep(3)
    future.set_result(1)

loop = asyncio.get_event_loop()
future = loop.create_future()
future.add_done_callback(bar)
loop.run_until_complete(foo(future))

替代方案:

async def foo():
    await asyncio.sleep(3)
    bar(1)

loop = asyncio.get_event_loop()
loop.run_until_complete(foo())

第二个版本什么时候不可用/合适?我一定错过了这一点?

1 回答

  • 2

    在如图所示的代码中,没有理由使用显式的future和 add_done_callback ,你总是可以 await . 一个更现实的用例是,如果情况发生逆转,如果 bar() 产生 foo() 并且需要访问其结果:

    def bar():
        fut = asyncio.create_task(foo())
        def when_finished(_fut):
            print("foo returned", fut.result())
        fut.add_done_callback(when_finished)
    

    如果这让你想起了"callback hell",那你就走在了正确的轨道上 - Future.add_done_callback 大致相当于pre-async / await JavaScript承诺的 then 运算符 . (细节不同,因为 then() 是一个返回另一个承诺的组合子,但基本思想是一样的 . )

    implementation asyncio的很大一部分是用这种风格编写的,使用了编排未来的普通函数 . 它感觉就像Twisted的现代化版本,在基于回调的传输和协议世界之上添加了协同程序和流作为更高级别的糖 . 使用此基本工具集编写的应用程序代码为like this .

    即使使用非协同回调,除了惯性或复制粘贴之外,很少有充分的理由使用 add_done_callback . 例如,上面的函数可以简单地转换为使用 await

    def bar():
        async def coro():
            ret = await foo()
            print("foo returned", ret)
        asyncio.create_task(coro())
    

    这比原始版本更具可读性,并且更容易适应更复杂的等待场景 . 将协同程序插入较低级别的asyncio管道是similarly easy .

    那么,当需要使用 Future API和 add_done_callback 时,用例是什么?我可以想到几个:

    • new combinators .

    • 使用更传统的回调样式编写的代码连接协同程序代码,例如thisthis .

    • 编写Python / C代码,其中 async def 不可用 .

    为了说明第一点,请考虑如何实现像 asyncio.gather() 这样的函数 . 它必须允许传递的协同程序/期货运行并等待它们全部完成 . 这里 add_done_callback 是一个非常方便的工具,允许该功能从所有期货请求通知而无需串联等待它们 . 在忽略异常处理和各种功能的最基本形式中, gather() 可能如下所示:

    async def gather(*awaitables):
        loop = asyncio.get_event_loop()
        futs = map(asyncio.ensure_future, awaitables)
        remaining = len(futs)
        finished = loop.create_future()
        def fut_done(fut):
            nonlocal remaining
            remaining -= 1
            if not remaining:
                finished.set_result(tuple(fut.result() for fut in futs))
        for fut in futs:
            fut.add_done_callback(fut_done)
        await finished
    

    即使你从不使用 add_done_callback ,它也是一个很好的工具,可以理解和了解你真正需要它的罕见情况 .

相关问题