我了解如何向将来添加回调方法,并在将来完成后调用它。但是,当您已经可以从协程内部调用函数时,为什么这样做有用呢?
回调版本:
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())
什么时候第二个版本不可用/不合适?
答案 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
的用例时,又是什么呢?我可以想到几个:
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
,它也是了解和了解您实际需要的罕见工具的好工具。