本文首先对asyncio模块的使用进行简单的介绍,然后着重分析asyncio中关于事件循环和协程的部分,而对于模块中在事件循环创建套接字连接等方法将会在另外的文章介绍
async def语句或使用@asyncio.coroutine装饰的生成器来实现可以使用asyncio模块的协程
基于生成器的协程应该使用yield from 而不是原始的yield语法
import asyncio
@asyncio.coroutine
def hello():
print("Hello world!")
r = yield from asyncio.sleep(1) #asyncio.sleep(1)是一个协程,在这里可以看成一个耗时1秒的IO操作
print("Hello again!")
loop = asyncio.get_event_loop() #获取当前上下文的事件循环对象
loop.run_until_complete(hello()) #运行事件循环,等待协程完成
loop.close() #关闭事件循环
@asyncio.coroutine装饰器把一个生成器函数标记为协程函数
yield from 等待asyncio.sleep(1)协程的完成,在此期间,主线程并未等待,而是去执行事件循环中其他可以执行的协程,因此可以实现并发执行。
在Python3.5中加入了新的语法async/await,分别用于替换@asyncio.coroutine和yield from
需要注意@asyncio.coroutine/yield from 和async/await两种方式不能混用
可以在在事件循环中执行多个协程
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
首先我们区分一下,将yield语句放置在赋值符号右边的生成器可以被称为协程,而使用async def定义 或@asyncio.coroutine修饰的函数也可以称为协程,但是后者是可以使用asyncio模块的Coroutine协程类型
同样"协程"一词可以用来指协程函数和协程对象。
协程函数 就是使用async def或用@asyncio.coroutine装饰的函数,可以使用asyncio.iscoroutinefunction(func)来判断
协程对象 就是通过调用协程函数获得的对象,可以使用asyncio.iscoroutine(obj)来判断
协程函数中通常由两种方式来结束执行
return expression 为通过await或yield from生成结果
raise expression 生成异常
在事件循环中轮询的对象都是Future对象,Task为Future的子类,也可在事件循环中,通常Future和Task没有什么本质区别,可以混用
Future对象是指添加到事件循环中,将要通过执行来生成结果或抛出异常的对象
使用在result = await/yield from future表达式中,挂起当前协程等待future完成,返回future结果或抛出异常,这两个都能传播
如果future被取消(future.canael()),那么抛出asyncio.CanceledError异常
使用loop.create_task()或asyncio.ensure_future()或loop.create_future()将future注册到事件循环中
这个两个可以联合使用 result = await asyncio.ensure_future(cor),将coroutine转换为future对象,注册到事件循环,然后再挂起当前的协程,等待future完成返回结果
调用协程并不会启动其代码运行,调用返回的协程对象在安排运行之前不会执行任何操作
使用在result = await/yield from coroutine表达式中,当前协程等待另一个协程返回生产结果或抛出异常
注意这里并没有将协程转换为future对象。。。。。而是直接等待协程生产结果
通过ensure_future()函数或create_task()方法转换为future对象,注册到事件循环中
事件循环会以future对象的注册顺序来轮询
现在简单说一下async和 @asyncio.coroutine装饰器的作用,除了将函数定义为协程函数外,还将函数中yield/ await/yield from等语句前的语句都被看作为事件循环中future对象预定执行的语句
事件循环初次启动就会开始按照注册顺序执行future对象中的预定执行语句
之后的启动会从上次停止位置开始执行,然后将控制返回事件循环对象,由事件循环开始轮询
这里需要注意的是await coroutine这个表达式是等待另一协程生产结果,因此这个协程中的预定义执行语句会安排到future对象中预定定义执行语句中
当执行完一个future对象中的所有预定语句后,在执行循环中下一个future对象预定执行语句
需要特别提醒的是,协程中会造成阻塞的语句会进行阻塞等待,比如time.sleep()无论他是在预定执行语句中,还是await等待的事件完成后的执行语句中
所以尽量,最好不要再协程中使用会造成阻塞的语句,会严重影响全局的性能,下一篇文章会介绍使用事件循环对象的run_inexecutor()方法利用concurrent.futures模块来异步的执行阻塞函数
Future对象有四种状态,Pending, running, done, cancelled
pending未决状态时,future对象从注册到事件循环到future对象完成或取消的状态
running状态就是在事件循环中正在执行的future对象,running状态是发生在pending态中的
需要注意的时在future的回调函数中如果调用Task.current_task()函数来查看当前运行的对象时会返回None,因为调用future对象的回调的条件是,future对象完成或者被取消(future.cancel() )
done状态就是生成结果或引发异常,或者被取消
注意cancelled状态也是done状态的一种
需要明确的是循环在第一次启动后要做的第一件事,或必须会做的一件事(当然如果KeyborderInterrupt引发程序终止就另当别论了)是按照启动前future对象的注册顺序来进入future对象中执行预定执行语句,再将控制交还给事件循环对象
而之后的启动会从上次停止的位置开始执行
首先讨论循环停止的位置
如果是在循环启动前调用loop.stop()方法,那么事件循环会执行完预定执行语句然后调用循环回调,再退出循环
如果是在循环启动后调用loop.stop()方法,就又要分两种情况来讨论
第一stop()方法在预定执行语句中调用,那么循环依然会执行完预定语句,然会调用毁掉,退出循环
第二种情况就是在await等语句之后调用stop()方法,那么就循环就会执行完成当前的future对象,但是future对象的回调函数不会执行,而是放在下一次循环再次启动时调用,并且执行在stop()调用前循环中为执行的预定执行语句(这个可以理解为调用stop()方法前又有新的future被注册到循环中,但是轮到该对象还没有拿到控制权,所以其中的预定语句还没有执行)
对于循环启动回调就比较简单了,事件循环对象回调是在循环拿到控制后立即调用
比如第一次启动执行完所有预定义语句后,控制会回到循环对象
或者当future对象在await语句后调用stop()方法,导致future完成但是还没有调用回调,而下次循环启动从future回调函数中开始执行,执行完后控制回到循环对象,执行事件循环对象回调
可以使用loop.call_soon()注册多个回调函数,并且回调函数遵循FIFO规则,先进先出
ascio模块是为编写在单线程中并发执行的代码,可以通过使用协程,套接字和其他资源的IO多路复用来运行网络客户端和服务器
本文仅介绍模块中对协程方面的介绍,而套接字等问题在另一篇文章介绍
asyncio的核心运行机制是事件循环,通过将协程对象注册到事件循环,使其根据事件的发生来循环调用
asyncio模块中定义了两个异常asyncio.TimeoutError,asyncio.InvlidStateError
asyncio.AbstractEventLoop 事件循环的基类
asyncio.BaseEventLoop 是事件循环的实现细节,为AbstractEventLoop的一个子类,同时也是asyncio中具体事件循环实现的基类
由于和循环的实现细节有关导致类的内部接口并不稳定,所以不应该直接使用,并且不应该被其他类来继承,应该使用AbstractEventLoop来代替
asyncio目前提供了两种事件循环的实现方式,SelectorEventLoop和PractorEventLoop
他们都是AbstractEventLoop的子类
asyncio.SelectorEventLoop类是基于selectors模块IO/多路复用来事件循环,
在Windows上,,只有套接字支持该类,管道不支持
asyncio.ProactorEventLoop
仅在Windows上可用,使用IO完成端口(IOCP)机制来实现事件循环
对事件循环的管理可以抽象为策略,通常,策略不需要进行显式的处理,使用默认的全局策略即可
以下默认访问全局策略
asyncio.get_event_loop() 获取当前上下文的事件循环,返回一个实现AbstractEventLoop接口的事件循环对象
asyncio.set_event_loop(loop) 将事件循环的当前上下文设置为loop
if sys.platform == 'win32':
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
asyncio.new_event_loop() 创建并返回一个新的事件循环对象
事件循环对象的方法
loop.run_forever() 运行直到调用stop(),事件循环一次,将超时时间设为0,所有预先安排好的响应io事件的回调将会立即执行并退出
如果run_forever()正在运行时调用stop(),将运行当前正在处理的事件会调用,并退出
loop.stop() 停止运行事件循环,使调用run_forever()的事件循环在下一个合适的机会退出
loop.run_until_complete(future) 运行Future对象直到完成,返回Future对象的执行结果,或引发的异常
loop.close()
关闭事件循环,该循环不能被运行,挂起的回调将会丢失
清除队列并关闭程序,但不会等待执行程序的完成
loop.is_running() 循环是否在运行
loop.is_closed() 循环是否关闭
loop.create_future() 创建Future对象并附加到该循环 python3.5.2中新增功能
loop.create_task(coro) 在循环中创建task,并使用future封装协程对象
loop.call_soon(callback, *args) 安排一个回调函数,当控制返回事件循环时会立即调用
可注册多个回调,遵循先进先出原则
回调函数的参数是通过,位置参数传入
可以使用functoolspartial()函数来传递关键字参数
loop.call_soon(functools.partial(print, "Hello", flush=True)) #将调用 print("Hello", flush=True).
future可翻译为期货,未来,将来的
Future类可以理解为将要在事件循环中执行的对象的类
asyncio.Future(*,loop=None) 创建Future对象
future.cancel() 取消future,安排回调
future.cancelled() 如果future被取消,则返回True
future.done() 如果future完成,则返回True
完成是指生成结果或引发异常,或者future对象被取消
future.result( ) 返回future的结果,如果future已取消,引发CancelledError,如果future的结果还不可用,引发InvalidStateError,如果ft完成并设置了异常,则引发此异常
future.exception( ) 返回future设置的异常,如果future已完成,返回异常,如果没有设置异常,则返回None,已取消返回CancelledError,还未完成,引发InvalidStateError
future.set_result(result) 标记future完成并设置结果,如果调用此方法时future已完成,则引发InvlidStateError
future.set_exception(exception) 标记future完成并设置异常,如果已完成,则引发InvildStateError
future.add_done_callback(fn) 添加回调以便future完成时运行,回调函数为接收使用ft作为参数的函数,
future.remove_done_callback(fn) 从调用完成的回掉函数列表中删除回调的所有实例,返回已删除的回调数量
asyncio.Task(coro, * ,loop=None)
Task为Future的子类,通过将协程coro封装为future对象来调度协程的执行
通常不会直接使用其构造方法来创建Task对象,而是使用asyncio.ensure_future()或loop.create_task()
Task.all_tasks(loop=None)
返回事件循环的所有任务,默认为当前事件循环
Task.current_task(loop=None)
返回事件循环中当前正在运行的任务
task.cancel() 请求取消任务自身
这将在事件循环的下一个循环中将CanceledError抛到包装的协程中,协程有机会使用try/except/finalyy请求甚至拒绝请求
和future.cancel不同,不能保证任务被取消,因为异常可能被捕获或执行,延迟任务的取消或完全阻止取消
当调用此方法后,cancelled()不会返回True,除非包装的协程被CancelledError异常终止时,任务才被标记为取消
asyncio.as_completed(fs, *, loop=None, timeout=None)
返回一个迭代器,值fs序列中完成(done状态)的Future实例
如果timeout在所有future完成之前发生,则引发TimeoutError错误
asyncio.ensure_future(coro_or_future, * , loop=None)
调度执行一个协程对象,封装为Future,返回task对象,如果参数为Future,则直接返回
如果loop为None使用默认事件循环
函数的作用相当于事件循环对象的create_task()方法
asyncio.async(coro_or_future, *, loop=None) ensure_future()在python3.4.4版本弃用的别名asyncio.gather(*coros_or_futures, loop=None,return_exception=False)
通过给定的协程或futures对象返回一个future汇总结果
所有future必须共享相同的事件循环,如果所有任务都完成了则返回的future对象的结果是结果列表(按照原始序列的顺序,不是futures结果到达的顺序)
return_exception参数为True,则任务中的异常被视为与成功结果相同,并在结果列表中收集
否则,第一个引发的异常将立即传播到返回的future
如果外部函数返回的future被取消,所有的没有完成的孩子也被取消
如果有任何一个孩子被取消引发CancelledError,但是返回的future对象并不会被取消,防止一个孩子的取消而影响其他的孩子也被取消
asyncio.iscoroutine(obj)
如果obj为协程对象,返回True
asyncio.iscoroutinefunction(func)
如果func为协程函数,则返回True
asyncio.shidle(arg,*, loop=None)
等待future,屏蔽它取消
res = from shield(something1())
res = yield from something2()
通常上边两个语句可以看作一样,如果调用他们的协程被取消了,那么something2同样会收到CancelError,而对于something()来说,取消被屏蔽,仍然运行,但是await依然会引发CancelledError
如果something1()被其他方法取消,仍然会被取消,当然可以借助try/except捕捉异常来完全忽略
asycnio.sleep(delay, result=None, *, loop=None)
创建一个给在给定时间(秒为单位)后完成的协程,当协程完成后将result参数返回给调用者,
asyncio.wait(futures,*, loop=None, timeout=None,return_when=ALL_COMPLETED)
等待序列futures中future和协程对象的完成,协程将被包裹在task中
返回包含两个Future集合的元组(done,pending)
done表示完成的future,pending表示超时发生时,未完成的future
序列futures不能为空,timeout控制返回前等待的时间,默认为None,表示等待时间没有限制
return_when表示函数应返回的时间
FIRST_COMPLETED 当任何未来完成或被取消时,该函数将返回
FIRST_EXCEPTION 当任何future通过抛出异常来完成时,函数将返回,如果没有future引发异常,则等同于ALL_COMPLETED
ALL_COMPLETED 当所有future完成或取消时,函数返回
asyncio.wait_for(fut,timeout, *, loop=None)
等待单个Future或协程对象完成,如果超时为None,则阻塞到future完成
协程将被包裹在task中
返回未来或协程的结果,超时发生,则取消任务并引发TimeoutError,比秒任务取消可封装在shield()中
如果等待被取消,future也会被取消