13、asyncio模块库:
13.1、asyncio异步协程的定义:
我们前面介绍生成器时介绍过协程,从生成器的调用过程我们可以理解协程的概念,协程是调用方和任务之间的协作方式,生成器中通过yield语句控制执行流程,通过调用__next__或send方法,使协程可以实现异步执行。
协程的一个重要特性是可以记住其上下文(执行的状态),使得调用方可以挂起协程暂停执行和恢复协程以接着之前的状态继续执行,调用方对“挂起”和“恢复”的循环操作,可以让多个任务交替执行以实现异步并发效果。
我们前面用yield语句实现过异步协程的并发效果,但本质上还是生成器,我们也用yield from 语句建立过委派生成器通道,使调用方可以通过委派生成器直接与子生成器通信,委派生成器可以嵌套调用,组成更长更复杂的生成器链,但是想实现异步效果就得写比较复杂的代码,显然不够Pythonic了。
现在我们用一种新的方法实现异步线程,我们先以asyncio库实现原生协程。
原生协程:
asyncio是用来编写并发代码的库,asyncio被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。asyncio 往往是构建 IO 密集型和高层级结构化网络代码的最佳选择。
我们对生成器循环调用__next__或send方法实现异步协程,asyncio 则用事件循环实现异步协程。进程或者线程都是把函数打包成进程或线程类实例,然后才能调用类方法(start())实现异步执行,进程和线程都是由计算机调度实现异步执行,类似的,协程由用户程序控制异步执行,asyncio会把协程函数打包成Task对象,把Task排入事件循环里(排入执行日程),由事件循环轮询执行Task(迭代Task列表),即“挂起”和“恢复”操作与协程函数一起都被打包进Task对象里,并由事件循环对Task轮询(迭代)执行。当一个 Task 对象被创建,它将复制当前上下文,然后在复制的上下文中运行其协程。
asyncio事件循环的基本执行过程为:事件循环会维护两个迭代队列,一个pending队列,放置的是等待执行的Task,一个是finished队列,放置的是执行完成的Task,事件循环从pending队列里取出Task执行,若是遇到挂起,即Task把控制权交还给事件循环,事件循环就把Task重新放入pending队列,再取出下一个Task执行,若是Task执行完全部代码,就放入finished队列,全部Task执行一遍后,再从pending队列重新轮询,未执行完的Task恢复之前的执行状态接着执行,如此循环,直到所有的Task都执行完。事件循环本身也是个“生成器”,可用stop停止,把控制权交给事件循环的调用方,调用方再次调用事件循环时,其会接着之前的上下文状态执行。
asyncio用装饰器@asyncio.coroutine把包含yield from语句的生成器装饰成协程函数,用 loop = asyncio.get_event_loop() 创建事件循环,用 loop.create_task(coroutine) 把协程打包成 Task,用 loop.run_until_complete(Task) 把 Task 排入事件循环并执行直至结束。我们用一个示例看下执行过程:
import time
import asyncio
@asyncio.coroutine
def child(t,task):
print(f"start_child time {time.strftime('%X')}",task)
yield from asyncio.sleep(t)
print(f"end_child time {time.strftime('%X')}",task)
@asyncio.coroutine
def task1(number):
print(f"start_task1 time {time.strftime('%X')}")
f = 0
for i in range(number):
f += i
print("task1 +%d" % i)
yield from child(1,task1.__name__)
print("task1 the end number =%d" % f)
print(f"end_task1 time {time.strftime('%X')}")
@asyncio.coroutine
def task2(number):
print(f"start_task2 time {time.strftime('%X')}")
f = 0
for i in range(number):
f *= i
print("task2 *%d" % i)
yield from child(1,task2.__name__)
print("task2 the end number =%d" % f)
print(f"end_task2 time {time.strftime('%X')}")
print(f"start_main time {time.strftime('%X')}")
loop = asyncio.get_event_loop()
task_1=loop.create_task(task1(2))
task_2=loop.create_task(task2(2))
loop.run_until_complete(task_1)
loop.run_until_complete(task_2)
#tasks = [loop.create_task(task1(2)),loop.create_task(task2(2))]
#loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print(f"end_main time {time.strftime('%X')}")
'''输出结果为:start_main time 14:32:48start_task1 time 14:32:48task1 + 0start_child time 14:32:48 task1start_task2 time 14:32:48task2 * 0start_child time 14:32:48 task2end_child time 14:32:49 task1task1 + 1start_child time 14:32:49 task1end_child time 14:32:49 task2task2 * 1start_child time 14:32:49 task2end_child time 14:32:50 task1task1 the end number =1end_task1 time 14:32:50end_child time 14:32:50 task2task2 the end number = 0end_task2 time 14:32:50end_main time 14:32:50'''
我们用装饰器@asyncio.coroutine把三个生成器装饰成协程函数,协程child嵌套在协程task1和task2中,由yield from语句引导,child内又嵌套了协程asyncio.sleep(t),表示等待t秒钟。
loop.run_until_complete(Task)把打包后的Task排入事件循环并执行,从输出结果可知:事件循环先执行task_1输出'start_task1 time 14:32:48',执行到 yield from 时开始执行child输出'start_child time 14:32:48 task1',在child中执行到 yield from 时开始执行等待asyncio.sleep(t),此时child交出了控制权给事件循环。
事件循环执行下一个任务task_2输出'start_task2 time 14:32:48',执行到 yield from 时开始执行child输出'start_child time 14:32:48 task2',同样在执行等待asyncio.sleep(t)时child交出了控制权给事件循环。
此时,事件循环已经把task_1、task_2执行了一遍,再次循环执行,接着task1之前的执行状态,输出'end_child time 14:32:49 task1',此时child执行完成,即yield from child(1,task1.__name__)执行完成,task1再次执行for循环,再次执行到child中的asyncio.sleep(t)时child交出了控制权给事件循环。
事件循环再次执行下一个任务task_2,接着task2之前的执行状态输出'end_child time 14:32:49 task2',此时child执行完成,即yield from child(1,task2.__name__)执行完成,task2再次执行for循环,再次执行到child中的asyncio.sleep(t)时child交出了控制权给事件循环。
此时,事件循环已经把task_1、task_2执行了第二遍,开始第三次循环执行,同理,Task接着之前的状态继续执行,task_1彻底执行完成,然后task_2也彻底执行完成,事件循环中的任务全部执行完,执行完的任务会自动从事件循环中移除,最后要用close()把不再使用的事件循环关闭。
从上述执行过程可知,事件循环对task_1、task_2实现的是异步执行。除了分别用loop.run_until_complete(Task)把task_1、task_2排入事件循环外,也可以把task_1、task_2放在列表tasks里,并用函数asyncio.wait(tasks)将任务排入事件循环。
上述原生