python-任务和协程文档翻译及延伸

679 查看

1. 协程概述

  • 能被asyncio调用的协程可以通过两种方式实现:

    1. 使用async def语句
    2. 使用生成器。

    第一种方式在Python3.5添加,在没有向下兼容的考虑时推荐使用。
    基于生成器的协程应该被@asyncio.coroutine 装饰,尽管这不是严格规定(not strictly enforced)。该装饰器能够兼容async def 定义的协程。基于生成器的协程使用 PEP 380引入的yield from语法,而不是原始的yield语法。

  • 协程一词正如生成器一词,被用于两个不同但相关的概念:
    1. 定义一个协程的函数。如果要消除歧义,我们称之为协程函数 (iscoroutinefunction() returns True)。
    2. 调用一个协程函数返回的对象。这个对象代表的运算和I/O操作最终将被完成。如果要消除歧义,我们称之为协程对象(iscoroutine() returns True)。
  • 协程能做的事情:
    1. result = await future或者result = yield from future
      暂停协程直到future被完成,然后返回future的结果,或者抛出一个异常,该异常将被传播。(如果future被取消,将抛出一个CancelledError异常。)要注意的是,tasks就是futures,一切关于futures的也适用于tasks
    2. result = await coroutine或者result = yield from coroutine
      等待另一个协程产出结果(或者抛出一个会被传播的异常)。协程表达式必须能被另一个协程调用。
    3. return expression
      产生结果给一个使用await 或yield from来等待该协程的另一个协程
    4. raise exception
      抛出异常一个使用await 或yield from来等待该协程的另一个协程
  • 调用一个协程并不启动它的代码运行——调用返回的协程对象什么也不做,直到你安排它执行。有两种基本的方式使它开始运行:

    1. 在另一个运行的协程中调用await coroutine或者yield from coroutine
    2. 使用ensure_future() 函数或者AbstractEventLoop.create_task() 方法来安排它的执行。
  • 协程(以及任务)只能在事件循环运行的时候执行。

2. @asyncio.coroutine

  • 它是一个标记基于生成器的协程的装饰器。它使得生成器使用yield from调用async def定义的协程,并且使该生成器能够被async def定义的协程调用(比如使用await语句调用)。
  • 没有必要去装饰用async def定义的协程它们自己。
  • 如果一个生成器在销毁前没有被yield from,一个错误信息将被日志记录。参看Detect coroutines never scheduled

3. 最简单的hello world 协程

import asyncio
async def hello_world(): 
    print("Hello World!")
loop = asyncio.get_event_loop()
# 阻塞式调用,直到hello_world()协程结束时返回
loop.run_until_complete(hello_world())
# run_until_complete只接受A Future, a coroutine or an awaitable
loop.close()
  • 相关:在Base Event Loop文档中的Hello World with call_soon():
    使用AbstractEventLoop.call_soon() 方法来安排一个回调。该回调打印“Hello World”然后停止事件循环。
    import asyncio
    #
    def hello_world(loop): 
      print('Hello World') 
      loop.stop()
    #
    loop = asyncio.get_event_loop()
    #
    # 安排一个hello_world()的调用
    loop.call_soon(hello_world, loop)
    #
    # 将被loop.stop()打断的阻塞式调用
    loop.run_forever()
    loop.close()

4. 打印当前时间的协程

  • 在5秒时间内每秒打印当前时间,利用了sleep() 函数
    import asyncio
    import datetime
    async def display_date(loop):
      end_time = loop.time() + 5.0
      while True:
          print(datetime.datetime.now())
          if (loop.time() + 1.0) >= end_time:
              break
          await asyncio.sleep(1)
          # asyncio.sleep睡眠指定时间再返回的协程,asyncio中有相应的调度机制
    loop = asyncio.get_event_loop()
    loop.run_until_complete(display_date(loop))
    loop.close()

打印结果

5. 链式协程

  • compute()协程被print_sum()等待:当compute()被执行完成并返回结果时print_sum()继续执行
    import asyncio
    async def compute(x, y):
      print("Compute %s + %s ..." % (x, y))
      await asyncio.sleep(1.0)
      return x + y
    async def print_sum(x, y):
      result = await compute(x, y)
      print("%s + %s = %s" % (x, y, result))
    loop = asyncio.get_event_loop()
    loop.run_until_complete(print_sum(1, 2))
    loop.close()

执行流程图

6. async/await 与asyncio

  • David Beazley指出async/await 实际上是异步编程的 API ,人们不应该将async/await等同于asyncio,而应该将asyncio看作是一个利用async/await API 进行异步编程的框架。
  • Python 3.5 协程究竟是个啥

7. 协程asyncio相关的优秀应用

  • asyncpg -- A fast PostgreSQL Database Client Library for Python/asyncio 一个快速的PostgreSQL数据库的客户端库,3倍于psycopg2。
  • uvloop -- uvloop is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses libuv under the hood. 内置事件循环器的快速、插入式替代品。
  • aiohttp -- http client/server for asyncio 异步http框架