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

时间:2018-12-10 08:21:23

标签: python-asyncio

我了解如何向将来添加回调方法,并在将来完成后调用它。但是,当您已经可以从协程内部调用函数时,为什么这样做有用呢?

回调版本:

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 个答案:

答案 0 :(得分:3)

在所示的代码中,没有理由使用明确的将来,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)

如果这使您想起“回调地狱”,那么您就走对了-Future.add_done_callback大致相当于then操作符,用于预异步/等待JavaScript承诺。 (细节有所不同,因为then()是一个返回另一个promise的组合器,但是基本思想是相同的。)

asyncio的实现的很大一部分是以这种样式编写的,使用的是编排期货的普通函数。感觉就像是Twisted的现代化版本,在基于回调的传输和协议世界的基础上,添加了协程和流作为高级糖。使用此基本工具集编写的应用程序代码看起来like this

即使使用非协程回调,除了惯性或复制粘贴之外,很少有很好的原因可以使用add_done_callback。例如,上面的函数可以简单地转换为使用await

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

比原始格式更具可读性,并且很多更容易适应更复杂的等待场景。将similarly easy插入协程到较低级的异步管道中。

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

  • new combinators
  • 将协程代码与以更传统的回调风格编写的代码(例如thisthis)连接。
  • async def不可用的情况下编写Python / C代码。

为说明第一点,请考虑如何实现类似asyncio.gather()的函数。它必须允许传递的协程/期货运行并等待它们中的所有完成。这里的add_done_callback是一个非常方便的工具,它允许函数从 all 请求期货的通知,而无需等待它们的序列。在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,它也是了解和了解您实际需要的罕见工具的好工具。