python中的asyncio之Coroutines,Tasks and Future

Coroutines and Tasks属于High-level APIs,也就是高级层的api。而Future属于低级层的api

Coroutines

使用async/await语法声明的协程是编写asyncio应用程序的首选方法。翻译过来就是协程的意思

import asyncio

async def main():
  print("hello")
  await asyncio.sleep(1)
  print("world")


if __name__ == '__main__':
  # asyncio.run(main()) # 3.7的用法
  # 阻塞直到hello world()协程结束时返回
  loop = asyncio.get_event_loop()
  loop.run_until_complete(main())

第一个异步函数是通过创建loop循环去调用,其他异步函数之间通过await进行调用。

asyncio.run方法是python3.7新增的一个高级层api

loop.run_until_complete(main())意思是如果是协程,则将其封装在任务对象中,内里调用的是ensure_future()方法,这个方法的意思装饰一个协程或者一个future,如果参数是future,则直接返回,而ensure_future()内里调用的是

create_task()方法,create_task(coro)传入一个协程函数的对象,返回任务对象

import asyncio
import time

async def say_after(delay, what):
  await asyncio.sleep(delay)
  print(what)


async def main():
  print(f"started at {time.strftime('%X')}")

  await say_after(1, 'hello')
  await say_after(2, 'world')

  print(f"finished at {time.strftime('%X')}")


if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  # 阻塞直到hello world()协程结束时返回
  loop.run_until_complete(main())
  loop.close()

 

Awaitables

我们说,如果一个对象可以用在await表达式中,那么它就是Awaitables的对象。
可等待对象主要有三种类型:coroutines, Tasks, and Futures.

Coroutines

协程函数:asyc def定义的函数;

协程对象:通过调用协程函数返回的对象。

Tasks

class asyncio.Task(coro, *, loop=None) 是 Future 的子类。因为 Future 没有保存其相关可执行对象的信息,我们 schedule the execution of a coroutine 这件事一般是通过 Task 对象来做的。

任务对协程进一步封装,其中包含任务的各种状态。

协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。

import asyncio


async def nested():
  await asyncio.sleep(2)
  print("等待2s")


async def main():
  # 将协程包装成任务含有状态
  # task = asyncio.create_task(nested())
  task = asyncio.ensure_future(nested())
  print(task)
  # "task" can now be used to cancel "nested()", or
  # can simply be awaited to wait until it is complete:
  await task
  print(task)
  print(task.done())


if __name__ == '__main__':
  loop = asyncio.get_event_loop()
  try:
    loop.run_until_complete(main())
  except KeyboardInterrupt as e:
    for task in asyncio.Task.all_tasks():
      print(task)
      task.cancel()
      print(task)
      loop.run_forever() # restart loop
  finally:
    loop.close()

 

可以看到

<Task pending coro=<nested() running at /Users/chennan/pythonproject/asyncproject/asyncio-cn/1-2-1.py:9>>
等待2s
<Task finished coro=<nested() done, defined at /Users/chennan/pythonproject/asyncproject/asyncio-cn/1-2-1.py:9> result=None>
True

创建task后,task在加入事件循环之前是pending状态然后调用nested函数等待2s之后打印task为finished状态。asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,python3.7增加了asyncio.create_task(coro)。其中task是Future的一个子类

Future

future:代表将来执行或没有执行的任务的结果。它和task上没有本质的区别,通常不需要在应用程序级别代码中创建Future对象。

future对象有几个状态:

  • Pending
  • Running
  • Done
  • Cancelled

注意 Future 并不包含可执行对象的本体,他只保存状态、结果、额外的回调函数这些东西。这也是上面称之为代理的原因。因为实际的调用过程是在 event loop 里发生的,event loop 负责在异步执行完成后向 future 对象写入 result 或 exception。这是异步任务的基本逻辑。

通过上面的代码可以知道创建future的时候,task为pending,事件循环调用执行的时候是running,调用完毕自然就是done于是调用task.done()打印了true。

如果在命令行中运行上述代码,ctrl+c后会发现输出以下内容

<Task pending coro=<nested() running at 1-2-1.py:9>>
^C<Task pending coro=<main() running at 1-2-1.py:21> wait_for=<Task pending coro=<nested() running at 1-2-1.py:10> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10d342978>()]> cb=[<TaskWakeupMethWrapper object at 0x10d342918>()]>>
<Task pending coro=<main() running at 1-2-1.py:21> wait_for=<Task pending coro=<nested() running at 1-2-1.py:10> wait_for=<Future cancelled> cb=[<TaskWakeupMethWrapper object at 0x10d342918>()]>>
<Task pending coro=<nested() running at 1-2-1.py:10> wait_for=<Future cancelled> cb=[<TaskWakeupMethWrapper object at 0x10d342918>()]>
<Task cancelling coro=<nested() running at 1-2-1.py:10> wait_for=<Future cancelled> cb=[<TaskWakeupMethWrapper object at 0x10d342918>()]>

因为我们调用了task.cancel() 所以可以看到此时的任务状态为取消状态

1、run_until_complete运行,会注册task(协程:print_sum)并开启事件循环 →

2、print_sum协程中嵌套了子协程,此时print_sum协程暂停(类似委托生成器),转到子协程(协程:compute)中运行代码,期间子协程需sleep1秒钟,直接将结果反馈到event loop中,即将控制权转回调用方,而中间的print_sum暂停不操作 →

3、1秒后,调用方将控制权给到子协程(调用方与子协程直接通信),子协程执行接下来的代码,直到再遇到wait(此实例没有)→

4、 最后执行到return语句,子协程向上级协程(print_sum抛出异常:StopIteration),同时将return返回的值返回给上级协程(print_sum中的result接收值),print_sum继续执行暂时时后续的代码,直到遇到return语句 →

5、向 event loop 抛出StopIteration异常,此时协程任务都已经执行完毕,事件循环执行完成(event loop :the loop is stopped),close事件循环。

event loop 对象包含两个部分:event 和 loop。event 负责 I/O 事件通知而 loop 负责循环处理 I/O 通知并在就绪时调用回调。这里 event 的含义与 select 中的 event mask 类似。

BaseEventLoop 类实现了基本的 loop 部分,而类似于 BaseSelectorEventLoop 这样的类实现了基于 selector 的 event 部分。

event loop 内部维护着两个容器:_ready 和 _scheduled。类型分别是 deque 和 list。_ready 代表已经可以执行,_scheduled 代表计划执行。_scheduled 中的 handle 是可以 cancel 的。

一次 loop 的基本流程可以参见 _run_once() 方法,其说明文档如下:

This calls all currently ready callbacks, polls for I/O, schedules the resulting callbacks, and finally schedules 'call_later' callbacks.

流程为:
  • 将 _scheduled 中已 canceled 的 handle 去掉

  • 检查 _ready 和 _scheduled 以确定一个用于 _selector.select() 的 timeout 值  

 timeout = None
 if self._ready:
     timeout = 0
 elif self._scheduled:
     # Compute the desired timeout.
     when = self._scheduled[0]._when
     timeout = max(0, when - self.time())
  • 通过 _selector.select() 获得一个 event_list 并 _process_events() 之
  • _process_events 即为将 得到的 events(handle)添加到 _ready 中
  • 顺序检查 _scheduled 将其中 .when() 到期的 handle 挪到 _ready 中
  • 顺序执行 _ready 中的 handle (handle._run())

故 eventloop 计划异步任务的基本方法就是将延时任务添加到 _scheduled 中,以及将即时任务添加到 _ready 中。延时任务的来源有 await futureloop.create_task() 等(最简单的方法应该是直接实例化 Future 实例,但这种做法除非在测试,一般不必用于真实业务中)。即时任务的来源基本有三种:call_soon() 的调用、_shceduled 到期,和 selector.select() 的返回。在 IO 处理中一般主要依赖第三种机制。

callback

callback 类型是普通的函数(不能是 coroutine)。

可以使用的方法有 call_soon 和 call_at。(call_later 是通过 call_at 实现的)

调用 call_soon 会将一个 Handle 压入 _ready

调用 call_at 会将一个 TimerHandle 压入 _scheduled

create_task

Task 用于处理 coroutine。底层机制上实际仍然依赖 callback。

posted @ 2019-03-18 14:31  {Dxd}  阅读(1227)  评论(0编辑  收藏  举报