异步介绍就不说了.
基础:python协程 python生成器 协程 yield yield from
异步基于事件循环 事件循环基础
在线程中运行与关闭事件循环:动态添加协程 关闭协程
补充材料[ 如不想了解 完全不会影响asyncio的理解和学习, 直接略过即可 ]:
C实现的select模型:I/O复用 select c/s 网络模型
windows下可以使用IOCP , 了解win下编程的可以看看IOCP的实现 : IOCP 完成端口服务器模型
协程只运行在事件循环中,
默认情况下asyncio.get_event_loop() 是一个select模型的事件循环,
默认事件循环 asyncio.get_event_loop() 属于主线程,
一个线程一个事件循环, 为什么要一个线程一个事件循环?
[ 这个东西不去了解完全不会影响asyncio包的学习, 如果不懂的话记住一个线程一个事件循环就得了]
简单来说 :事件循环分2个,一个select 模型, 一个win下可用的完成端口模型,而不论select还是IOCP
一般都是死循环 后面调用select 阻塞函数 , iocp的通知消息也是通过线程池中的死循环调用GetQueuedCompletionStatus来完成;
如果要了解一下select / IOCP模型 上面都贴了链接
如果你要使用多个事件循环 , 创建线程后调用
lp = asyncio.new_event_loop() #创建一个新的事件循环
asyncio.set_event_loop(lp) #设置当前线程的事件循环
核心思想: yield from / await . 就这2个关键字, 运行(驱动)一个协程, 同时交出当前函数的控制权,让事件循环执行下个任务.
yield from的实现原理:yield from实现
要搞懂asyncio协程,还是先把生成器弄懂,如果对生成器很模糊,
比如yield from 生成器对象,这个看不懂的话,建议先看python生成器 yield from
有2个方式让协程运行起来,协程(生成器)本身是不运行的
1. await / yield from 协程
2.asyncio.ensure_future/async(协程)
上面2个方式的区别是 await / yield from 这一组能等待协程完成
注意:
1. 协程就是生成器的增强版(多了send 与 yield 的接受), 在asyncio中的协程 与 生成器对象不同点:
asyncio协程: 函数内部不能使用 yield [如果使用会抛RuntimeError], 只能使用yield from / await,
一般的生成器: yield 或 yield from 2个都能用, 至少使用一个
因此在下面我就把协助和生成器混合在一起说了, 这2个本来就是一回事,除了不能使用yield
2.在asycio中所有的协程都被自动包装成一个个Task/Future对象, 但其本质还是一个生成器,因此可以
yield from /await Task/Futrue
基本流程:
1.定义一个协程 (async def 或者 @asyncio.coroutine 装饰的函数)
2.调用上述函数,获取一个协程对象 [不能使用yield ,除非你自己写异步模块,毕竟最终所调用的还是基于yield的生成器函数]
2.1
通过asyncio.ensure_future 或 asyncio.async 函数调度协程(这部意味着要开始执行了) ,返回了一个Task对象
Task对象是Future对象的子类, (这步可作可不作,只要是一个协程对象,一旦仍进事件队列中,将自动给你封装成Task对象)
3.获取一个事件循环 asyncio.get_event_loop() ,默认此事件循环属于主线程
4.等待事件循环调度协程
*后面的例子着重说明了一下as_completed ,附加了源码. 先说明一下:
1. as_completed 每此迭代返回一个协程,
2.这个协程内部从Queue从取出先完成的Future对象
3.然后我们再 await coroutine
5.最后说明一下事件循环. 可以动态的增加协程到事件循环中. 而不是在一开始就确定所有需要协程.
5.1. asyncio.
get_event_loop
() 默认情况下,这个事件循环是主线程的
下面例子中 asyncio.ensure_future/async都可以换成asyncio.run_coroutine_threadsafe[在不同线程中的事件循环] :
"""
第一个例子
没什么用.
注意: 协程 与 生成器 的用法是一样的. 需要调用之后才产生对象.
"""
@asyncio.coroutine #装饰一个普通函数. 当成协程
def func():
print('hi')
lp = asyncio.get_event_loop() #获取事件循环
lp.run_until_complete(func()) #仍进事件循环里.注意,func() 而不是func. 需要调用之后才是协程对象.
"""
用async def (新语法) 定义一个函数,同时返回值
asyncio.sleep 模拟IO阻塞情况 ; await 相当于 yield from.
await 或者 yield from 交出函数控制权(中断),让事件循环执行下个任务 ,一边等待后面的协程完成
"""
async def func(i):
print('start')
await asyncio.sleep(i)#交出控制权
print('done')
return i
co = func(2) #产生协程对象
print(co)
lp = asyncio.get_event_loop() #获取事件循环
task = asyncio.async(co) #开始调度
lp.run_until_complete(task) #等待完成
print(task.result()) #获取结果
"""
添加一个回调:add_done_callback
"""
async def func(i):
print('start')
await asyncio.sleep(i)
return i
def call_back(v):
print('callback , arg:',v,'result:',v.result())
co = func(2) #产生协程对象
lp = asyncio.get_event_loop() #获取事件循环
task = asyncio.async(co) #开始调度
task.add_done_callback(call_back) #增加回调
lp.run_until_complete(task) #等待
print(task.result()) #获取结果
一个事件循环中执行多个task , 并发执行:
1.wait , gather 2个函数都是用于获取结果的. 且都不阻塞,直接返回一个生成器对象可用于 yield from / await
2.两种用法 .
第一种: result = asyncio.run_until_completed(asyncio.wait/gather) 执行所有完成之后获取
第二种在链中: result = await asyncio.wait/gather 在一个协程内获取结果
3. as_completed 与并发包concurrent 中的行为类似, 哪个任务先完成哪个先返回 , 内部实现是yield from Queue.get()
4. 嵌套: await / yield from 后跟协程. 直到后面的协程运行完毕, 才执行await/yield from下面的代码, 整个过程是不阻塞的
见下面各例子:
"""
并发.执行多个任务.
调度一个Task对象列表
调用asyncio.wait 或者 asyncio.gather 获取结果
"""
async def func(i):
print('start')
await asyncio.sleep(i) #交出控制权,事件循环执行下个任务,同时等待完成
return i
tasks = [asyncio.async(func(i)) for i in range(3)] #开始调度一个Task对象列表
lp = asyncio.get_event_loop()
lp.run_until_complete(asyncio.wait(tasks))
for task in tasks:
print(task.result())
"""
通过await 或者 yield from 形成1个链, 后面跟其他协程. 形成一个链的目的很简单,
当前协程需要这个结果才能继续执行下去.
就跟普通函数调用其他函数获取结果一样
"""
async def func(i):
print('start')
await asyncio.sleep(i)
return i
async def to_do():
print('to_do start')
tasks = []
#开始调度3个协程对象
for i in range(3):
tasks.append(asyncio.ensure_future(func(i)))
#在协程内等待结果. 通过await 来交出控制权, 同时等待tasks完成
task_done,task_pending = await asyncio.wait(tasks)
print('to_do get result')
#获取已经完成的任务
for task in task_done:
print('task_done:', task.result())
#未完成的
for task in task_pending:
print('pending:',task)
lp = asyncio.get_event_loop() #获取事件循环
lp.run_until_complete(to_do()) #把协程对象放进去
lp.close() #关闭事件循环
as_completed:这个函数的行为比较好玩 ,返回一个迭代器,每次迭代一个协程.
简单说一下,内部有一个Queue(queue.Queue 线程安全) , 先完成的先入队.
as_completed迭代的协程源码是 : 注意 yield from 后面可以跟 iterable
#简化版代码
f = yield from done.get() # done 是 Queue
return f.result()
例子:
asyncio.as_completed 返回一个生成器对象 , 因此可用于迭代
每次从此生成器中返回的对象是一个个协程(生成器),哪个最先完成哪个就返回, 而要从生成器/协程中获取返回值,
就必须使用yield from / await , 简单来说就是:生成器的返回值在异常中, 详情参考最上面的基础链接
async def func(x):
# print('\t\tstart ',x)
await asyncio.sleep(5)
# print('\t\tdone ', x)
return x
async def to_do():
#在协程内调度2个协程
tasks = [asyncio.ensure_future(func(i)) for i in range(2)]
#使用as_completed:先完成,先返回.
#每次迭代返回一个协程.
#这个协程:_wait_for_one,内部从队列中产出一个最先完成的Future对象
for coroutine in asyncio.as_completed(tasks):
result = await coroutine #等待协程,并返回先完成的协程
print('result :',result)
print('all done')
lp = asyncio.get_event_loop()
lp.set_debug(True)
lp.run_until_complete(to_do()) #调度协程
下面关于事件循环的, 事件循环中维护了一个队列(FIFO, Queue) , 通过另一种方式来调用:
"""
事件循环中维护了一个FIFO队列
通过call_soon 通知事件循环来调度一个函数.
"""
def func(x):
print('x:', x, ',start time:', time.ctime())
time.sleep(x)
print('func invoked:',x)
loop = asyncio.get_event_loop()
loop.call_soon(func,1) #调度一个函数
loop.call_soon(func,2)
loop.call_soon(func,3)
loop.run_forever() #阻塞
可以看到以上操作是同步的.
接下来把通过asyncio.
run_coroutine_threadsafe 函数可以把上述函数调度变成异步执行:
"""
1.首先会调用asyncio.run_coroutine_threadsafe 这个函数.
2.之前的普通函数修改成协程对象
"""
async def func(x):
print('x:',x ,',start time:',time.ctime())
await asyncio.sleep(x)
print('func invoked:',x, ',now:',time.ctime())
loop = asyncio.get_event_loop()
co1 = func(1)
co2 = func(2)
co3 = func(3)
asyncio.run_coroutine_threadsafe(co1,loop) #调度
asyncio.run_coroutine_threadsafe(co2,loop)
asyncio.run_coroutine_threadsafe(co3,loop)
loop.run_forever() #阻塞
上面2个例子只是告诉你2件事情.
1.run_coroutine_threadsafe(异步线程安全) 和call_soon (同步).
2.run_coroutine_threadsafe 这个函数 对应 ensure_future(只能作用于同一线程中).
可以在一个子线程中运行一个事件循环,然后在主线程中动态的
添加协程,这样既不阻塞主线程执行其他任务,子线程也可以异步的执行协程.
需要注意的是: 默认情况下获取的event_loop 是主线程的. 所以要在子线程中使用event_loop 需要new_event_loop .如果
在子线程中直接获取event_loop 会抛异常 .源代码中的判断:
isinstance(threading.current_thread(), threading._MainThread)
例子:
import asyncio,time,os,sys,queue,threading
"""
call_soon , call_soon_threadsafe 是同步的
asyncio.run_coroutine_threadsafe(coro, loop) -> 对应 asyncio.ensure_future
事件循环中,异步执行.
"""
#在子线程中执行一个事件循环 , 注意需要一个新的事件循环
def thread_loop(loop:asyncio.AbstractEventLoop):
print('线程开启 tid:',threading.currentThread().ident)
asyncio.set_event_loop(loop) #设置一个新的事件循环
loop.run_forever()
async def func(x,q):
print('func :', x , ',time:',time.ctime() , ',tid:',threading.currentThread().ident)
await asyncio.sleep(x)
q.put(x)
q = queue.Queue()
lp = asyncio.new_event_loop() #新建一个事件循环 , 如果使用默认的,则不能放入子线程
t = threading.Thread(target=thread_loop,args=(lp,))
t.start()
co1 = func(2,q) #2个协程
co2 = func(3,q)
asyncio.run_coroutine_threadsafe(co1,lp) #开始调度在子线程中的事件循环
asyncio.run_coroutine_threadsafe(co2,lp)
print('开始事件:',time.ctime())
while 1:
x = q.get()
print('main :',x,',time:',time.ctime())