我了解如何向将来添加回调方法,并在将来完成后调用它。但是,当您已经可以从协程内部调用函数时,这为何有用呢?
回调版本:
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))
Run Code Online (Sandbox Code Playgroud)
选择:
async def foo():
await asyncio.sleep(3)
bar(1)
loop = asyncio.get_event_loop()
loop.run_until_complete(foo())
Run Code Online (Sandbox Code Playgroud)
什么时候第二个版本不可用/不合适?
在所示的代码中,没有理由使用明确的将来,并且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)
Run Code Online (Sandbox Code Playgroud)
如果这使您想起“回调地狱”,那么您处在正确的位置- Future.add_done_callback等效then于pre-async / await JavaScript promises 的运算符。(细节有所不同,因为then()是返回另一个promise的组合器,但是基本思想是相同的。)
asyncio的大部分实现都是使用这种样式编写的,使用的是编排未来的普通函数。感觉就像是Twisted的现代化版本,在基于回调的传输和协议世界的基础上,添加了协程和流作为高级糖。使用此基本工具集编写的应用程序代码如下所示。
即使使用非协程回调,除惯性或复制粘贴外,也很少有使用的充分理由add_done_callback。例如,上面的函数可以简单地转换为使用await:
def bar():
async def coro():
ret = await foo()
print("foo returned", ret)
asyncio.create_task(coro())
Run Code Online (Sandbox Code Playgroud)
这比原始内容更具可读性,并且更容易适应更复杂的等待场景。这是同样容易堵塞协同程序进入下级ASYNCIO管道。
那么,当需要使用API 时,用例又是什么呢?我可以想到几个:Futureadd_done_callback
为了说明第一点,请考虑如何实现类似的功能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
Run Code Online (Sandbox Code Playgroud)
即使您从未使用过add_done_callback,它也是了解和了解您实际需要的罕见工具的好工具。
| 归档时间: |
|
| 查看次数: |
1493 次 |
| 最近记录: |