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
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() 所以可以看到此时的任务状态为取消状态
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 future
、loop.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。