27

Guido van Rossum 在 2014 年关于 Tulip/Asyncio 的演讲中展示了这张幻灯片

任务与协程

  • 相比:

    • res = 来自 some_coroutine(...)
    • res = 来自任务的产量(some_coroutine(...))
  • 任务无需等待即可取得进展

    • 就像你在等待别的东西一样记录
      • 即产量来自

我完全没有抓住重点。

从我的角度来看,这两种结构是相同的:

在裸协程的情况下 - 它被调度,因此无论如何都会创建任务,因为调度程序与任务一起运行,然后协程调用者协程被挂起,直到被调用者完成,然后可以自由继续执行。

万一Task- 都一样 - 新任务被调度并且调用者协程等待它的完成。

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

ps
非常感谢权威来源(GvR、PEP、文档、核心开发人员说明)的链接。

4

3 回答 3

29

对于调用方来说,协程yield from coroutine()感觉就像一个函数调用(即当 coroutine() 完成时它将再次获得控制权)。

yield from Task(coroutine())另一方面,感觉更像是创建一个新线程。Task()几乎立即返回,并且很可能调用者在coroutine()完成之前重新获得控制权。

f()和之间的区别th = threading.Thread(target=f, args=()); th.start(); th.join()很明显,对吧?

于 2014-11-23T10:36:07.337 回答
15

使用点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直接使用,因为文档推荐它

不要直接创建Task实例:使用async()函数或BaseEventLoop.create_task()方法。

* 请注意,从 Python 3.4.4 开始,asyncio.async不推荐使用asyncio.ensure_future.

于 2014-11-30T02:34:51.123 回答
3

如 PEP 380 中所述,已接受的 PEP 文档引入了 yield from,表达式res = yield from f()来自以下循环的思想:

for res in f():
    yield res

有了这个,事情就变得很清楚了:如果f()some_coroutine(),那么协程就会被执行。另一方面, if f()is Task(some_coroutine()),Task.__init__被执行。some_coroutine()不执行,只有新创建的生成器作为第一个参数传递给Task.__init__.

结论:

  • res = yield from some_coroutine()=> 协程继续执行并返回下一个值
  • res = yield from Task(some_coroutine())=> 创建了一个新任务,其中存储了一个未执行的some_coroutine()生成器对象。
于 2014-12-03T22:14:42.717 回答