假设我们有一个虚拟函数:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
有什么区别:
import asyncio
coros = []
for i in range(5):
coros.append(foo(i))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))
以及:
import asyncio
futures = []
for i in range(5):
futures.append(asyncio.ensure_future(foo(i)))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))
- 注意 *:这个例子返回了一个结果,但这不是问题的重点。当返回值很重要时,使用
gather()
代替wait()
。
不管返回值是什么,我都在寻找ensure_future()
的清晰性。wait(coros)
和wait(futures)
都运行协程,那么什么时候以及为什么要将协程 Package 在ensure_future
中呢?
基本上,使用Python 3.5的async
运行一系列非阻塞操作的正确方法是什么?
如果我想批处理调用,该怎么办?例如,我需要调用some_remote_call(...)
1000次,但我不想用1000个同时连接压垮web服务器/数据库/等。这可以用线程或进程池实现,但有没有办法用asyncio
实现呢?
2020年更新(Python 3.7+):不要使用这些代码段,而用途:
import asyncio
async def do_something_async():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(foo(i)))
await asyncio.gather(*tasks)
def do_something():
asyncio.run(do_something_async)
还可以考虑使用Trio,这是一个健壮的第三方asyncio替代品。
5条答案
按热度按时间5hcedyr01#
协程是一个生成器函数,它既可以生成值,也可以从外部接收值。使用协程的好处是我们可以暂停函数的执行,稍后再继续执行。在网络操作的情况下,在等待响应时暂停函数的执行是有意义的。我们可以利用这段时间运行其他函数。
future就像Javascript中的
Promise
对象,它就像一个占位符,代表一个值,这个值将在未来被具体化。在上面提到的例子中,在等待网络I/O时,一个函数可以给予我们一个容器,一个承诺,当操作完成时,它将用这个值填充这个容器。我们持有future对象,当它被实现时,我们可以调用它的一个方法来检索实际结果。**直接回答:**如果您不需要结果,则不需要
ensure_future
。如果您需要结果或检索出现异常,则ensure_future
非常有用。**额外学分:**我会选择
run_in_executor
并传递一个Executor
示例来控制最大工作线程数。说明和样本代码
在第一个例子中,我们使用的是协程,
wait
函数接收一组协程并将它们组合在一起,所以wait()
在所有协程都用完时结束(completed/finished返回所有值)。run_until_complete
方法将确保循环在执行完成之前是活动的。请注意,在这种情况下,您无法获得异步执行的结果。在第二个示例中,您使用
ensure_future
函数 Package 协程并返回Task
对象,该对象是Future
的一种。当您调用ensure_future
时,协程计划在主事件循环中执行。返回的future/task对象还没有值,但随着时间的推移,当网络操作完成时,未来对象将保存操作的结果。所以在这个例子中,我们做了同样的事情,除了我们使用future而不是仅仅使用协程。
让我们看一个如何使用asyncio/coroutines/futures的例子:
这里,我们在
loop
对象上使用了create_task
方法。ensure_future
将在主事件循环中调度任务。这个方法使我们能够在所选的循环中调度协程。我们还看到了使用任务对象上的
add_done_callback
方法添加回调的概念。当协程返回一个值、引发异常或被取消时,
Task
就是done
。有一些方法可以检查这些事件。我写了一些博客文章,这些主题可能会有所帮助:
当然,你可以在官方手册上找到更多细节:https://docs.python.org/3/library/asyncio.html
mv1qrgav2#
靶区;DR
async def
)将不会运行它。它返回一个协程对象,就像生成器函数返回生成器对象一样。await
从协程中检索值,即“调用”协程。eusure_future/create_task
Package 一个协程,并安排它在下一次迭代的事件循环中运行,但不会等待它完成,它就像一个守护线程。一些代码示例
让我们先明确一些术语:
async def
s;await
的东西,像任务,未来或普通的协程对象。术语
coroutine
可以是协程函数,也可以是协程对象,这取决于上下文,但是您应该很容易区分它们。案例1,协程上的
await
我们创建了两个协程,一个
await
,使用create_task
运行另一个。你会得到这样的结果,普通协程按预期首先执行:
因为
coro
是直接执行的,而task
是在下一次迭代中执行的。情况2,将控制权交给事件循环
通过调用
asyncio.sleep(1)
,控制权被交还给循环,我们应该看到一个不同的结果:你会得到这样的结果,执行顺序是相反的:
调用
asyncio.sleep(1)
时,控制权被交还给事件循环,循环检查要运行的任务,然后首先运行由create_task
创建的task
。虽然我们首先调用了协程函数,但没有使用
await
,我们只是创建了一个协程,它不会自动启动。然后,我们创建了一个新的协程,并通过create_task
调用将其 Package ,creat_task
不仅 Package 了协程,还调度了任务在下一次迭代中运行。结果,create_task
在plain await
之前执行。这里的神奇之处在于将控制权交还给循环,您可以使用
asyncio.sleep(0)
来实现相同的结果。在所有的差异之后,同样的事情是:如果你等待一个协程或者一个 Package 协程的任务,也就是一个可等待的,你总是可以取回它们返回的结果。
引擎盖下
asyncio.create_task
调用asyncio.tasks.Task()
,asyncio.tasks.Task()
将调用loop.call_soon
。loop.call_soon
将任务放入loop._ready
。在循环的每次迭代期间,它检查loop._ready
中的每个回调并运行它。asyncio.wait
、asyncio.ensure_future
和asyncio.gather
实际上直接或间接地调用loop.create_task
。在文件中还应注意:
回调函数是按照它们注册的顺序调用的。每个回调函数只调用一次。
krcsximq3#
Vincent的一条评论链接到https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346,它显示
wait()
为您将协程 Package 在ensure_future()
中!换句话说,我们确实需要一个未来,而协程将默默地转化为未来。
当我找到关于如何批处理协程/future的明确解释时,我会更新这个答案。
slsn1g294#
From the BDFL [2013]
任务
*等待[其他东西]
记住这一点,
ensure_future
作为创建Task的名字是有意义的,因为Future的结果将被计算,无论你是否等待它(只要你等待一些东西),这允许事件循环在你等待其他事情的时候完成你的Task,注意在Python 3.7中create_task
是确保Future的首选方式。1yjd4xko5#
虽然已经有了一些非常有用的答案,但它们并没有涵盖所有的细微差别,特别是,公认的答案不再是正确的。
您不应该将
wait
与协程一起使用-为了与新版本的库兼容。文件:
自3.8版起已弃用,将在3.11版中删除:不推荐直接向wait()传递协同程序对象。
文档中的另一个声明可能对深入理解有用。
wait
的结果是futures。如果你想检查你的协程是否在result中,你应该先用create_task
将它 Package 到future中(因为它是比ensure_future
更好的创建任务的方法)。wait()自动将协同程序调度为Tasks,稍后返回(done,pending)集合中隐式创建的Task对象,因此下面的代码无法正常工作:
下面是如何修复上面的代码片段: