python-asyncio - future.add_done_callback() 的用例是什么?
问题描述
我了解如何将回调方法添加到未来并在未来完成时调用它。但是,当您已经可以从协程内部调用函数时,为什么这会有所帮助?
回调版本:
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())
第二个版本什么时候不可用/不合适?
解决方案
In the code as shown, there is no reason to use an explicit future and add_done_callback
, you could always await
. A more realistic use case is if the situation were reversed, if bar()
spawned foo()
and needed access to its result:
def bar():
fut = asyncio.create_task(foo())
def when_finished(_fut):
print("foo returned", fut.result())
fut.add_done_callback(when_finished)
If this reminds you of "callback hell", you are on the right track - Future.add_done_callback
is a rough equivalent of the then
operator of pre-async/await JavaScript promises. (Details differ because then()
is a combinator that returns another promise, but the basic idea is the same.)
A large part of asyncio is implemented in this style, using non-async functions that orchestrate async futures. That basic layer of transports and protocols feels like a modernized version of Twisted, with the coroutines and streams implemented as a separate layer on top of it, a higher-level sugar. Application code written using the basic toolset looks like this.
Even when working with non-coroutine callbacks, there is rarely a good reason to use add_done_callback
, other than inertia or copy-paste. For example, the above function could be trivially transformed to use await
:
def bar():
async def coro():
ret = await foo()
print("foo returned", ret)
asyncio.create_task(coro())
This is more readable than the original, and much much easier to adapt to more complex awaiting scenarios. It is similarly easy to plug coroutines into the lower-level asyncio plumbing.
So, what then are the use cases when one needs to use the Future
API and add_done_callback
? I can think of several:
- Writing new combinators.
- Connecting coroutines code with code written in the more traditional callback style, such as this or this.
- Writing Python/C code where
async def
is not readily available.
To illustrate the first point, consider how you would implement a function like asyncio.gather()
. It must allow the passed coroutines/futures to run and wait until all of them have finished. Here add_done_callback
is a very convenient tool, allowing the function to request notification from all the futures without awaiting them in series. In its most basic form that ignores exception handling and various features, gather()
could look like this:
async def gather(*awaitables):
loop = asyncio.get_event_loop()
futs = list(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(None) # wake up
for fut in futs:
fut.add_done_callback(fut_done)
await finished
# all awaitables done, we can return the results
return tuple(f.result() for f in futs)
Even if you never use add_done_callback
, it's a good tool to understand and know about for that rare situation where you actually need it.
推荐阅读
- c# - 如何模拟电信号(Enigma 模拟器)
- java - 无法使用批处理文件运行 selenium。收到警告为无法链接和确定类的方法,错误为无法读取类上的方法
- mysql - 需要将此 mysql 表与子字符串相加以找到正确的值
- html - 如何在组件选择器上添加禁用属性?
- java - 尝试在 Ubuntu 上运行 jshell 时,当前未安装程序“jshell”
- c++ - C++ 将 unsigned char 数组转换为字符串表示形式的 long(或 long long)
- reactjs - React DirectLine 聊天不起作用。找不到源文件
- hazelcast - Hazelcast Mancenter 启用/禁用快照
- spring - 为什么应用程序/json获取请求上的Spring解码+(加号)?我该怎么办?
- c# - 带有 API 密钥的广告系列监控帖子