在asyncio的前篇中我们已经知道yield到asyncIO的演化路线
现在我们就来了解asyncio的基本使用
event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数(协程)注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
future 对象:代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。Task 对象是 Future 的子类,它将 coroutine 和 Future 联系在一起,将 coroutine 封装成一个 Future 对象。
async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。
import asyncio
async def hello(name):
print(‘Hello,’, name)
#定义协程对象
coroutine = hello(“World”)
#定义事件循环对象容器
loop = asyncio.get_event_loop()
#task = asyncio.ensure_future(coroutine)
#将协程转为task任务
task = loop.create_task(coroutine)
#将task任务扔进事件循环对象中并触发
loop.run_until_complete(task)
await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。
注意这里是,一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。就是你不能在生成器中使用await,也不能在async 定义的协程中使用yield。
除此之外呢,还有一点很重要的。
yield from 后面可接 可迭代对象,也可接future对象/协程对象;
await 后面必须要接 future对象/协程对象
绑定回调函数
异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调了。
回调的实现,有两种,一种是绝大部分程序员喜欢的,利用的同步编程实现的回调。 这就要求我们要能够有办法取得协程的await的返回值。
import asyncio
import time
async def _sleep(x):
time.sleep(2)
return '暂停{}秒'.format(x)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(_sleep(2))
loop.run_until_complete(task)
#取得返回结果
print(task.result())
运行结果
暂停2秒
- 还有一种是通过asyncio自带的添加回调函数功能来实现。
import asyncio
import time
async def _sleep(x):
time.sleep(2)
return '暂停{}秒'.format(x)
def callback(future):
print("这里是回调函数,获取返回结果是:",future.result())
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(_sleep(2))
#回调函数,自动将future传递进去
task.add_done_callback(callback)
loop.run_until_complete(task)
运行结果:
这里是回调函数,获取返回结果是: 暂停2秒
上面讲述的是协程中的单任务,而很多情况下我们其实是要进行多任务的,否则异步IO就没意义了
协程中的并发
asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。
我们采用wait和gather来处理多个并发
import asyncio
async def do_some_work(x):
print('waiting',x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
#协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(3)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
#loop.run_until_complete(asyncio.gather(*tasks))
for i in tasks:
print(i.result())
协程中的嵌套
使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。
import asyncio
# 用于内部的协程函数
async def do_some_work(x):
print('Waiting', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
# 用于外部的协程函数
async def main():
# 创建协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(3)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
# dones表示已经完成的任务
# pendings 表示未完成的任务,因为wait是可以设定等待时间的
dones, pendings = await asyncio.wait(tasks)
for i in dones:
print("task ret:", i.result())
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
源码当中也是嵌套的:
asyncio.await()的源码# 内部协程函数
async def _wait(fs, timeout, return_when, loop):
assert fs, 'Set of Futures is empty.'
waiter = loop.create_future()
timeout_handle = None
if timeout is not None:
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
counter = len(fs)
def _on_completion(f):
nonlocal counter
counter -= 1
if (counter <= 0 or
return_when == FIRST_COMPLETED or
return_when == FIRST_EXCEPTION and (not f.cancelled() and
f.exception() is not None)):
if timeout_handle is not None:
timeout_handle.cancel()
if not waiter.done():
waiter.set_result(None)
for f in fs:
f.add_done_callback(_on_completion)
try:
await waiter
finally:
if timeout_handle is not None:
timeout_handle.cancel()
done, pending = set(), set()
for f in fs:
f.remove_done_callback(_on_completion)
if f.done():
done.add(f)
else:
pending.add(f)
return done, pending
# 外部协程函数
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs:
raise ValueError('Set of coroutines/Futures is empty.')
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}')
if loop is None:
loop = events.get_event_loop()
fs = {ensure_future(f, loop=loop) for f in set(fs)}
# 【重点】:await一个内部协程
return await _wait(fs, timeout, return_when, loop)
协程中的状态
生成器的时候,有提及过生成器的状态。同样,在协程这里,我们也了解一下协程(准确的说,应该是Future对象,或者Task任务)有哪些状态。
Pending:创建future,还未执行
Running:事件循环正在调用执行任务
Done:任务执行完毕
Cancelled:Task被取消后的状态
gather与wait的的深入理解
还是照例用例子来说明,先定义一个协程函数
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number+1):
print(“Task %s: Compute factorial(%s)…” % (name, i))
await asyncio.sleep(1)
f *= i
print(“Task %s: factorial(%s) = %s” % (name, number, f))
- 接受参数方面的差异:
asyncio.wait
接收的tasks,必须是一个list对象,这个list对象里,存放多个的task。
它可以这样,用asyncio.ensure_future转为task对象
tasks=[
asyncio.ensure_future(factorial("A", 2)),
asyncio.ensure_future(factorial("B", 3)),
asyncio.ensure_future(factorial("C", 4))
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
也可以这样,不转为task对象。
loop = asyncio.get_event_loop()
tasks=[
factorial("A", 2),
factorial("B", 3),
factorial("C", 4)
]
loop.run_until_complete(asyncio.wait(tasks))
asyncio.gather
接收的就比较广泛了,他可以接收list对象,但是 * 不能省略
tasks=[
asyncio.ensure_future(factorial("A", 2)),
asyncio.ensure_future(factorial("B", 3)),
asyncio.ensure_future(factorial("C", 4))
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
还可以这样,和上面的 * 作用一致,这是因为asyncio.gather()的第一个参数是 *coros_or_futures,它叫 非命名键值可变长参数列表,可以集合所有没有命名的变量。
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
))
甚至还可以这样
loop = asyncio.get_event_loop()
group1 = asyncio.gather(*[factorial("A" ,i) for i in range(1, 3)])
group2 = asyncio.gather(*[factorial("B", i) for i in range(1, 5)])
group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)])
loop.run_until_complete(asyncio.gather(group1, group2, group3))
- 返回结果不同
asyncio.wait 返回dones和pendings
dones:表示已经完成的任务
pendings:表示未完成的任务
如果我们需要获取,运行结果,需要手工去收集获取。
asyncio.gather
它会把值直接返回给我们,不需要手工去收集。
results = await asyncio.gather(*tasks)
for result in results:
print('Task ret: ', result)
- wait有控制功能,是wait的优点
import asyncio
import random
async def coro(tag):
await asyncio.sleep(random.uniform(0.5, 5))
loop = asyncio.get_event_loop()
tasks = [coro(i) for i in range(1, 11)]
# 【控制运行任务数】:运行第一个任务就返回
# FIRST_COMPLETED :第一个任务完全返回
# FIRST_EXCEPTION:产生第一个异常返回
# ALL_COMPLETED:所有任务完成返回 (默认选项)
dones, pendings = loop.run_until_complete(asyncio.wait(tasks,return_when="FIRST_COMPLETED"))
print("第一次完成的任务量:",len(dones))
dones2,pendings2 = loop.run_until_complete(asyncio.wait(pendings,timeout=1))
print("第二次完成的任务量:",len(dones2))
dones3,pendings3 = loop.run_until_complete(asyncio.wait(pendings2))
print("第三次完成的任务量:",len(dones3))
loop.close()
运行结果:
第一次完成的任务数: 1
第二次完成的任务数: 4
第三次完成的任务数: 5
以上是asyncio的基本用法,而其实在实际当中asyncio并不是单独使用,现在很多异步开发的库都是以asyncio为基础的。