asyncio提供的框架以事件循环(event loop)为中心,程序开启一个无限的循环,程序会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
事件循环loop调用方法loop.run_until_complete(…)驱动传入的参数,它的参数是一个future或者协程。如果是协程,run_until_complete方法和wait函数一样,把协程包装进一个Task对象中。协程、future和任务(Task)都由yield from驱动,这正是run_until_complete方法对其参数所做的事情。
yield from foo句法能够防止阻塞,是因为当前协程(即包括yield from代码的委派生成器)暂停后,控制权回到事件循环手中,再去驱动其它协程; foo future或协程运行结束后,把结果返回到暂停的协程(yield from自动捕获StopIteration),将其恢复。这样,最外层的事件循环始终可以不被阻塞,并发地调度所有协程向前推进。
Future
future是一个数据结构,表示还未完成的工作结果。事件循环可以监视Future对象是否完成。从而允许应用的一部分等待另一部分完成一些工作。
Task
task是Future的一个子类,它知道如何包装和管理一个协程的执行。任务所需的资源可用时,事件循环会调度任务允许,并生成一个结果,从而可以由其他协程消费。
await
await 用于挂起阻塞的异步调用,用于挂起耗时的操作。
使用await可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行await 后面跟着的那个协程,直到await来的协程也挂起或者执行完毕,再进行下一个协程的执行。
1、简单的创建一个协程
async关键字定义一个协程(coroutine),协程也是一种对象。协程不能直接运行,需要把协程加入到事件循环(loop),由后者在适当的时候调用协程。
asyncio.get_event_loop 方法可以创建一个事件循环。
然后使用 run_until_complete 将协程注册到事件循环,并启动事件循环。
下面是3.6版本的创建协程方法。3.7的更加方便了,可参考官方文档:asyncio
import asyncio
#@asyncio.coroutine # 3.4 版本,用这个方法声明是协程的方式。
#def func(n):
async def func(n): # 3.4之后,用 async 加在 def 前面,表示这是一个协程对象。
print('hello world <{}>'.format(n))
# r = yield from asyncio.sleep(n) # 3.4 版本,调用方式。
r = await asyncio.sleep(3) # 3.4之后 用 await 调用 。
print('hello again<{}>'.format(n))
loop = asyncio.get_event_loop() # 获取消息循环 Eventloop
loop.run_until_complete(func(2)) # 把协程对象注册到事件循环中。(只注册一个协程对象的方法。)
tasks = [func(4), func(5)] # 注册多个协程对象的方法。
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程对象包装成为了一个任务(task)对象。所谓task对象是Future类的子类。保存了协程运行后的状态,用于未来获取协程的结果。
2、创建一个task
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine) 都可以创建一个task,run_until_complete 的参数是一个 futrue对象。当传入一个协程,其内部会自动封装成task,task是Future的子类。isinstance(task, asyncio.Future) 将会输出True。
import asyncio
import time
async def do_some_work(x):
await asyncio.sleep(3)
print('Waiting: ', x)
return 'do_some_work 的返回值 2222。'
start = time.time()
coroutine = do_some_work('随便一个参数')
loop = asyncio.get_event_loop()
# task = asyncio.ensure_future(coroutine) # 利用asyncio.ensure_future 生成一个task对象。
task = loop.create_task(coroutine) # 利用 loop.create_task() 生成一个task对象。
print('未注册到循环时的Task:{}'.format(task))
loop.run_until_complete(task) # task 对象注册到事件循环中。
print('注册到循环后的Task:{}'.format(task))
print('Task.result() 值:{}'.format(task.result())) # result() 方法获取返回协程的return值。
print('总耗时: ', time.time() - start)
运行结果为:
未注册到循环时的Task:<Task pending coro=<do_some_work() running at F:/procedure/进程、线程/协程/test.py:5>>
Waiting: 随便一个参数
注册到循环后的Task:<Task finished coro=<do_some_work() done, defined at F:/procedure/进程、线程/协程/test.py:5> result=None>
Task.result() 值:do_some_work 的返回值 2222。
总耗时: 3.0001378059387207
上面的 协程还可以绑定一个 回调函数,如下:
import asyncio
import time
def call_back(f):
print('协程执行完后的调用')
print(f'传入的参数为:{f.result()}')
async def do_some_work(x):
await asyncio.sleep(3)
print('Waiting: ', x)
return 'do_some_work 的返回值。'
start = time.time()
coroutine = do_some_work('随便一个参数')
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
# task = loop.create_task(coroutine)
task.add_done_callback(call_back) # 绑定回调函数,它会把 do_some_work(x) 最后的return 值当作参数 传给 call_back()
loop.run_until_complete(task)
print('总耗时: ', time.time() - start)
3 、协程状态
future对象有几个状态:
Pending
Running
Done
Cancelled
创建future的时候,task为pending,事件循环调用执行的时候当然就是running,调用完毕自然就是done,如果需要停止事件循环,就需要先把task取消。可以使用asyncio.Task获取事件循环的task。
import itertools
import sys, asyncio
async def spin(msg): # 这个协程用来逐个显示 “ | / - \ ”,就像一条线在转圈圈。
write, flush = sys.stdout.write, sys.stdout.flush # 把标准的输入、输出赋值给变量 write, flush。
for char in itertools.cycle('|/-\\'): # itertools.cycle() 无限循环。
status = char + ' ' + msg
write(status) # 把 status写入到标准输出中,即可以在cmd里显示。
flush() # 刷新缓存,使其马上显示到屏幕上。
write('\x08' * len(status)) # \x08 退格符和\b等效,用来清空屏幕的输出显示。
try:
await asyncio.sleep(0.1) # 暂停 0.1 秒,让出本协程对线程的占用。
except asyncio.CancelledError:
break
async def slow_function(): # 假设这是 处理 I/O事件的耗时计算。
await asyncio.sleep(5) # 用协程的 asyncio.sleep() 让出 占用时间。
return 2222
async def supervisor(): # 该协程函数用来创建一个可以注册到循环事件中的future对象。
spinner = asyncio.ensure_future(spin('thinking……')) # 把 spin() 协程函数生成 task 对象。
print(f'spinner object: {spinner}')
result = await slow_function() # 用 await 来执行 slow_function() 这个协程。但这个协程是挂起5秒钟。
spinner.cancel() # 5秒后,向Task(协程)传入取消信号,会在 spin() 的 await 处抛出 asyncio.CancelledError 错误。
return result
def main():
loop = asyncio.get_event_loop() # 获取消息循环 Eventloop
result = loop.run_until_complete(supervisor())
loop.close()
print(f'Answer: {result}', flush=True)
if __name__ == '__main__':
main()