问题:协程和Python 3.5中的future / task之间的区别?
假设我们有一个虚拟函数:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
之间有什么区别:
coros = []
for i in range(5):
coros.append(foo(i))
loop = get_event_loop()
loop.run_until_complete(wait(coros))
和:
from asyncio import ensure_future
futures = []
for i in range(5):
futures.append(ensure_future(foo(i)))
loop = get_event_loop()
loop.run_until_complete(wait(futures))
注意:该示例返回结果,但这不是问题的重点。如果返回值很重要,请使用gather()
代替wait()
。
无论返回值如何,我都希望在上保持清晰ensure_future()
。wait(coros)
并且wait(futures)
都运行协程,那么何时以及为什么要包装协程ensure_future
?
基本上,使用Python 3.5运行一堆非阻塞操作的正确方法(tm)是async
什么?
为了获得额外的抵免额,如果我要批量处理电话,该怎么办?例如,我需要拨打some_remote_call(...)
1000次,但我不想同时连接1000个连接而粉碎Web服务器/数据库/等。这对于线程或进程池是可行的,但是有没有办法做到这一点asyncio
?
回答 0
协程是生成器函数,它既可以产生值也可以从外部接受值。使用协程的好处是我们可以暂停函数的执行并在以后恢复它。在网络操作的情况下,在我们等待响应的同时暂停函数的执行是有意义的。我们可以花时间运行其他功能。
未来就像Promise
Javascript中的对象一样。它就像一个占位符,代表着将在未来实现的价值。在上述情况下,在等待网络I / O时,一个函数可以给我们一个容器,保证在操作完成时它将用值填充该容器。我们保留了将来的对象,当它满足时,我们可以在其上调用方法以检索实际结果。
直接回答:你并不需要ensure_future
,如果你不想要的结果。如果您需要结果或检索发生的异常,它们会很好。
额外积分:我将选择run_in_executor
并传递一个Executor
实例来控制最大工人数。
说明和示例代码
在第一个示例中,您正在使用协程。该wait
函数接收一堆协程并将它们组合在一起。这样就wait()
完成了所有协程的耗尽(返回所有值的完成/完成)。
loop = get_event_loop() #
loop.run_until_complete(wait(coros))
该run_until_complete
方法将确保循环有效直到执行完成。请注意在这种情况下您如何无法获得异步执行的结果。
在第二个示例中,您将使用ensure_future
函数包装协程并返回的Task
对象Future
。协程计划在您调用时在主事件循环中执行ensure_future
。返回的future / task对象还没有值,但是随着时间的推移,当网络操作完成时,future对象将保存操作的结果。
from asyncio import ensure_future
futures = []
for i in range(5):
futures.append(ensure_future(foo(i)))
loop = get_event_loop()
loop.run_until_complete(wait(futures))
因此,在此示例中,我们正在做相同的事情,除了使用期货而不是仅使用协程。
让我们看一下如何使用asyncio /协程/期货的示例:
import asyncio
async def slow_operation():
await asyncio.sleep(1)
return 'Future is done!'
def got_result(future):
print(future.result())
# We have result, so let's stop
loop.stop()
loop = asyncio.get_event_loop()
task = loop.create_task(slow_operation())
task.add_done_callback(got_result)
# We run forever
loop.run_forever()
在这里,我们create_task
在loop
对象上使用了方法。ensure_future
将在主事件循环中安排任务。这种方法使我们能够在选择的循环中安排协程。
我们还看到了add_done_callback
在任务对象上使用方法添加回调的概念。
A Task
是done
当协程返回值,引发异常或被取消时。有检查这些事件的方法。
我写了一些有关这些主题的博客文章,可能会有所帮助:
- http://masnun.com/2015/11/13/python-generators-coroutines-native-coroutines-and-async-await.html
- http://masnun.com/2015/11/20/python-asyncio-future-task-and-the-event-loop.html
- http://masnun.com/2015/12/07/python-3-using-blocking-functions-or-codes-with-asyncio.html
当然,您可以在官方手册上找到更多详细信息:https : //docs.python.org/3/library/asyncio.html
回答 1
简单的答案
- 调用协程函数(
async def
)不会运行它。它返回一个协程对象,就像生成器函数返回生成器对象一样。 await
从协程中检索值,即“调用”协程eusure_future/create_task
安排协程在下一次迭代时在事件循环上运行(尽管不等待它们完成,就像守护线程一样)。
一些代码示例
让我们先清除一些术语:
- 协程功能,您所需要
async def
的; - 协程对象,当您“调用”协程函数时得到的内容;
- 任务,一个包裹在协程对象上的对象在事件循环上运行。
案例1,await
在协程上
我们创建两个协程,await
一个协程,并用于create_task
运行另一个协程。
import asyncio
import time
# coroutine function
async def p(word):
print(f'{time.time()} - {word}')
async def main():
loop = asyncio.get_event_loop()
coro = p('await') # coroutine
task2 = loop.create_task(p('create_task')) # <- runs in next iteration
await coro # <-- run directly
await task2
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
您将得到结果:
1539486251.7055213 - await
1539486251.7055705 - create_task
说明:
task1直接执行,而task2在以下迭代中执行。
情况2,将控制权交给事件循环
如果替换main函数,则会看到不同的结果:
async def main():
loop = asyncio.get_event_loop()
coro = p('await')
task2 = loop.create_task(p('create_task')) # scheduled to next iteration
await asyncio.sleep(1) # loop got control, and runs task2
await coro # run coro
await task2
您将得到结果:
-> % python coro.py
1539486378.5244057 - create_task
1539486379.5252144 - await # note the delay
说明:
调用时asyncio.sleep(1)
,该控件已退回到事件循环,该循环检查要运行的任务,然后运行由创建的任务create_task
。
请注意,我们首先调用协程函数,但不是await
,因此,我们只创建了一个协程,而不使其运行。然后,我们再次调用协程函数,并将其包装在create_task
调用中,creat_task实际上将调度协程在下一次迭代中运行。因此,结果create task
是在之前执行await
。
实际上,这里的重点是将控制权交还给循环,您可以asyncio.sleep(0)
用来查看相同的结果。
引擎盖下
loop.create_task
实际通话asyncio.tasks.Task()
,将会通话loop.call_soon
。并将loop.call_soon
任务放入loop._ready
。在循环的每次迭代期间,它将检查loop._ready中的每个回调并运行它。
asyncio.wait
,asyncio.ensure_future
并且asyncio.gather
实际上loop.create_task
直接或间接调用。
另请注意文档:
回调按注册顺序调用。每个回调将仅被调用一次。
回答 2
文森特(Vincent)的评论链接到https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346,显示为您wait()
包装了协程ensure_future()
!
换句话说,我们确实需要未来,协程将默默地转变为它们。
当我找到有关如何协程/期货的明确解释时,我将更新此答案。
回答 3
任务
- 这是包裹在未来的协程
- Task类是Future类的子类
- 因此,它与工作的await呢!
- 它与裸协程有何不同?
- 无需等待就可以取得进步
- 只要您等待其他事情,即
- 等待 [something_else]
- 只要您等待其他事情,即
考虑到这一点,ensure_future
将其作为创建任务的名称是有意义的,因为无论您是否等待它(只要您等待某事),都会计算Future的结果。这使事件循环可以在您等待其他事情时完成您的任务。请注意,Python 3.7 create_task
是确保未来的首选方法。
注意:出于现代性的考虑,我将Guido幻灯片中的“收益率从”更改为“等待”。