来自协程的产量与来自任务的产量

时间:2014-11-22 10:55:42

标签: python python-3.x asynchronous concurrency python-asyncio

Guido van Rossum在2014年关于Tulip / Asyncio的演讲中shows the slide

  

任务与协同程序

     
      
  • 比较

         
        
    • res =来自some_coroutine(...)的收益
    •   
    • res =来自任务的收益(some_coroutine(...))
    •   
  •   
  • 任务可以在不等待的情况下取得进展

         
        
    • 当你等待其他东西时记录      
          
      • 即。收益来自
      •   
    •   
  •   

我完全忽略了这一点。

从我的观点来看,两种结构都是相同的:

如果是裸协程 - 它会被调度,所以无论如何都会创建任务,因为调度程序与Tasks一起运行,然后协同调用程序协同程序被暂停,直到被调用者完成,然后可以自由继续执行。

如果Task - 完全相同 - 新任务被调度,调用者协程等待其完成。

在这两种情况下执行代码的方式与开发人员在实践中应考虑的影响有何不同?

P.S。
与权威来源(GvR,PEP,docs,核心开发者笔记)的链接将非常受欢迎。

3 个答案:

答案 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.asyncTask实例包裹在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()生成器对象。
相关问题