简介
asyncio是一种使用单线程单进程的的方式实现并发的工具。asyncio提供的框架以事件循环(event loop)为中心,程序开启一个无限的循环,程序会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
event loop
Eventloop实例提供了注册、取消和执行任务(Task)和回调的方法。
把一些异步函数注册到这个事件循环上,事件循环会循环执行这些函数(但同时只能执行一个),当执行到某个函数时,如果它正在等待I/O返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成I/O后,会恢复下次循环到它的时候继续执行。因此,这些异步函数可以协同(Cooperative)运行
协程(Coroutine)
协程本质上是一个函数,特点是在代码块中可以将执行权交给其他协程。
import asyncio
async def a():#async定义协程
print('Suspending a')
await asyncio.sleep(0)#await表示调用协程,sleep 0并不会真的sleep,但却交出控制权
print('Resuming a')
async def b():
print('In b')
async def main():
await asyncio.gather(a(), b())#并发运行任务,在这里表示协同的执行a和b两个协程
if __name__ == '__main__':
asyncio.run(main())
#运行结果:表示在运行协程a的过程中让出了控制权,并发执行协程b,之后再返回执行a
#Suspending a
#In b
#Resuming a
#也可以不用run函数调用协程
loop = asyncio.get_event_loop()
try:
result = loop.run_until_complete(main())#运行事件循环,直到main运行结束
finally:
loop.close()#关闭时间循环
Future
future是一个数据结构,表示还未完成的工作结果。事件循环可以监视Future对象是否完成。从而允许一个协程等待另一协程完成一些工作
Task
Task是Future的一个子类,它知道如何包装和管理一个协程的执行。任务所需的资源可用时,事件循环会调度任务允许,并生成一个结果,从而可以由其他协程消费。
task = asyncio.ensure_future(a())
#或者是task = loop.create_task(a())
print(task.done())#False
await task#真正开始执行
print(task.done())#True
Event
import asyncio
async def waiter(event):
print('waiting for it ...')
await event.wait()
print('... got it!')
async def main():
# Create an Event object.
event = asyncio.Event()
# Spawn a Task to wait until 'event' is set.
waiter_task = asyncio.create_task(waiter(event))
# Sleep for 1 second and set the event.
await asyncio.sleep(1)
event.set()
# Wait until the waiter task is finished.
await waiter_task
asyncio.run(main())
#在这里例子中可以发现,并不能把await认为是执行协程,而是等待协程,当创建了waiter的task后,waiter就已经开始执行了,所以打印了'waiting for it ...',但是因为在等待event的set信号,所以在此阻塞。
#在sleep1秒后,执行了set,所以又转到协程waiter去执行,并且执行打印'... got it!'
#如果把await waiter_task放到sleep前面,那么就会一直等待set信号,并且等待不到
#如果不执行await waiter_task,照样会打印出waiter的两行语句
执行协程的例子
#下面是一个错误的例子
import asyncio
import time
async def say_after(delay, what):#定义了一个协程函数
await asyncio.sleep(delay)
print(what)
async def main():#同样也是协程
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')#顺序执行两个协程
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
#结果是并没有实现两个协程并发执行,因为等待的时间有3s
async def main():#惯用写法是在协程main中执行其他的协程
task1 = asyncio.create_task(#将协程函数用Task封装
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1#也是顺序执行两个协程
await task2
print(f"finished at {time.strftime('%X')}")
#结果是真正实现了两个协程的并发执行,因为执等待了2s
#注意错误的写法,下面的不能实现协程的并发执行
#await asyncio.create_task(say_after(1, 'hello'))
#await asyncio.create_task(say_after(2, 'world'))
#使用下面的形式也可以实现协程的并发执行
async def main():
await asyncio.gather(say_after(1, 'hello'), say_after(2, 'world'))
async def main():
await asyncio.wait([say_after(1, 'hello'), say_after(2, 'world')])
#通过event_loop也可以创建task和future
loop=asyncio.get_event_loop()
loop.create_future(coroutine) #返回future对象
loop.create_task(corootine) #返回task对象
可等待对象await
可以在await语句中使用的对象,有三种,协程,Future和Task,只是通常情况下没有必要在应用代码中创建 Future 对象。不能在同步/普通函数里使用await,否则会出错。但是在协程中可以调用另外一个协程(使用await),也可以调用普通函数。
import asyncio
async def nested():
return 42
async def main():
nested()#此协程并没有执行
print(await nested()) #使用await才会真正执行
asyncio.run(main())
import asyncio
async def nested():
return 42
async def main():
task = asyncio.create_task(nested())
await task
asyncio.run(main())
函数签名
asyncio.sleep(delay, result=None)
#阻塞 delay 秒,如果指定了 result,则当协程完成时将其返回给调用者。
#sleep() 总是会挂起当前任务,以允许其他任务运行。
#将 delay 设为 0 将提供一个经优化的路径以允许其他任务运行。 这可供长期间运行的函数使用以避免在函数调用的全过程中阻塞事件循环。
asyncio.wait_for(aw, timeout)在timeout秒内等待waitable对象的完成
#举例,等待时间太久导致异常
async def eternity():
# Sleep for one hour
await asyncio.sleep(3600)
print('yay!')
async def main():
# Wait for at most 1 second
try:
await asyncio.wait_for(eternity(), timeout=1.0)
except asyncio.TimeoutError:
print('timeout!')
asyncio.run(main())
# Expected output:
#
# timeout!
asyncio.to_thread(func, /, *args, **kwargs)
#在不同的线程中异步地运行函数func,函数参数*args 和 **kwargs
def blocking_io():#这是一个普通函数
print(f"start blocking_io at {time.strftime('%X')}")
time.sleep(1)#这里是一个同步函数,并不是asyncio.sleep
print(f"blocking_io complete at {time.strftime('%X')}")
async def main():
print(f"started main at {time.strftime('%X')}")
await asyncio.gather(
asyncio.to_thread(blocking_io),
asyncio.sleep(1))
print(f"finished main at {time.strftime('%X')}")
asyncio.run(main())
# Expected output:
#
# started main at 19:50:53
# start blocking_io at 19:50:53
# blocking_io complete at 19:50:54
# finished main at 19:50:54
#在任何协程中直接调用 blocking_io() 将会在调用期间阻塞事件循环,导致额外的 1 秒运行时间。 但是,通过改用 asyncio.to_thread(),我们可以在单独的线程中运行它从而不会阻塞事件循环。
#阻塞事件循环的写法,将blocking_io()在main中单独执行,asyncio.gather只执行asyncio.sleep