Guido van Rossum在2014年关于Tulip / Asyncio的演讲中shows the slide:
任务与协同程序
比较
- res =来自some_coroutine(...)的收益
- res =来自任务的收益(some_coroutine(...))
任务可以在不等待的情况下取得进展
- 当你等待其他东西时记录
- 即。收益来自
我完全忽略了这一点。
从我的观点来看,两种结构都是相同的:
如果是裸协程 - 它会被调度,所以无论如何都会创建任务,因为调度程序与Tasks一起运行,然后协同调用程序协同程序被暂停,直到被调用者完成,然后可以自由继续执行。
如果Task
- 完全相同 - 新任务被调度,调用者协程等待其完成。
在这两种情况下执行代码的方式与开发人员在实践中应考虑的影响有何不同?
P.S。
与权威来源(GvR,PEP,docs,核心开发者笔记)的链接将非常受欢迎。
答案 0 :(得分:27)
对于主叫方共同例程yield from coroutine()
感觉就像一个函数调用(即当coroutine()完成时它将再次获得控制权。)
yield from Task(coroutine())
更像是创建一个新线程。 Task()
几乎立即返回,很有可能调用者在coroutine()
完成之前获得控制权。
f()
和th = threading.Thread(target=f, args=()); th.start(); th.join()
之间的区别很明显,对吧?
答案 1 :(得分:13)
使用asyncio.Task(coro())
的目的是针对不想要明确等待coro
但您希望coro
执行的情况等待其他任务时的背景。这就是Guido的滑动意味着
[A]
Task
可以在不等待的情况下取得进展... 只要您等待 其他东西
考虑这个例子:
import asyncio
@asyncio.coroutine
def test1():
print("in test1")
@asyncio.coroutine
def dummy():
yield from asyncio.sleep(1)
print("dummy ran")
@asyncio.coroutine
def main():
test1()
yield from dummy()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
输出:
dummy ran
正如您所看到的,test1
从未实际执行过,因为我们没有在其上明确调用yield from
。
现在,如果我们使用asyncio.async
将Task
实例包裹在test1
周围,结果会有所不同:
import asyncio
@asyncio.coroutine
def test1():
print("in test1")
@asyncio.coroutine
def dummy():
yield from asyncio.sleep(1)
print("dummy ran")
@asyncio.coroutine
def main():
asyncio.async(test1())
yield from dummy()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
输出:
in test1
dummy ran
所以,使用yield from asyncio.async(coro())
确实没有实际的理由,因为它比yield from coro()
慢,没有任何好处;它引入了将coro
添加到内部asyncio
调度程序的开销,但这不是必需的,因为使用yield from
可以保证coro
将要执行。如果您只想调用协程并等待它完成,只需直接yield from
协程。
旁注:
我直接使用asyncio.async
*代替Task
because the docs recommend it:
不要直接创建
Task
个实例:使用async()
函数或BaseEventLoop.create_task()
方法。
*请注意,从Python 3.4.4开始,不推荐asyncio.async
使用asyncio.ensure_future
。
答案 2 :(得分:3)
如PEP 380中所述,从表达式res = yield from f()
引入收益的公认PEP文档来自以下循环的概念:
for res in f():
yield res
有了这个,事情变得非常清楚:如果f()
是some_coroutine()
,则执行协程。另一方面,如果f()
为Task(some_coroutine())
,则执行Task.__init__
。 some_coroutine()
未执行,只有新创建的生成器作为第一个参数传递给Task.__init__
。
结论:
res = yield from some_coroutine()
=> coroutine继续执行并返回下一个值res = yield from Task(some_coroutine())
=>创建一个新任务,该任务存储未执行的some_coroutine()
生成器对象。