asyncio简明教程
1. 前言
asyncio 是用来编写并发代码的库,使用 async/await 语法。
asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。asyncio 往往是构建 IO 密集型的最佳选择。
asyncio 是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。如果你在 Python 早期版本中,想使用类似的功能,可以了解一下 gevent 库和 twisted 库。
2. asyncio定义
asyncio 模块提供了一个围绕事件循环的框架。事件循环就是等待事件发生,然后对事件进行操作,依赖这种方式实现异步IO。
- event_loop:时间循环,开启之后,可以将协程注册进来。
- task:一个协程对象就是一个可以挂起的函数,任务是对协程的进一步封装,其中包含了任务的各种状
- future:代表将来执行或没有执行的任务的结果。task可以说是future的子类。
下面以一段代码为例子
import asyncio
@asyncio.coroutine
def hello1():
print("request https://www.xtuz.net 1")
# 异步调用asyncio.sleep(1):
yield from asyncio.sleep(3)
print("response https://www.xtuz.net 1")
@asyncio.coroutine
def hello2():
print("request https://www.xtuz.net 2")
# 异步调用asyncio.sleep(1):
yield from asyncio.sleep(2)
print("response https://www.xtuz.net 2")
# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [hello1(), hello2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
在asyncio库中,协程使用 @asyncio.coroutine
装饰来定义一个Task,并将放入到 event_loop 的循环中,使用 yield from
来驱动。
上述代码输出如下:
request https://www.xtuz.net 1
request https://www.xtuz.net 2
response https://www.xtuz.net 2
response https://www.xtuz.net 1
在python3.5中作了如下更改
@asyncio.coroutine => async
yield from => await
基于此,我们可以将上述代码进行改写,代码看起来要逻辑清晰很多。
import asyncio
async def hello1():
print("request https://www.xtuz.net 1")
# 异步调用asyncio.sleep(1):
await asyncio.sleep(3)
print("response https://www.xtuz.net 1")
async def hello2():
print("request https://www.xtuz.net 2")
# 异步调用asyncio.sleep(1):
await asyncio.sleep(2)
print("response https://www.xtuz.net 2")
# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [hello1(), hello2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
2.1 创建Task
loop.create_task()
接收一个协程,返回一个 asyncio.Task 的实例,也是 asyncio.Future 的实例。
返回值可直接传入 run_until_complete()
2.2 获取Task返回值
task.result()
可通过调用 task.result() 方法来获取协程的返回值,但是只有运行完毕后才能获取,若没有运行完毕,result()方法不会阻塞去等待结果,而是抛出 asyncio.InvalidStateError 错误。
2.3 控制Task
asyncio.wait()
asyncio.wait() 是一个协程,不会阻塞,立即返回,返回的是协程对象。传入的参数是future或协程构成的可迭代对象。最后将返回值传给run_until_complete()加入事件循环。
2.4 动态添加Task
- *loop.call_soon_threadsafe() * 等待此函数返回后马上调用回调函数,返回值是一个 asyncio.Handle 对象,此对象内只有一个方法为 cancel()方法,用来取消回调函数。
- *loop.call_soon() * 与call_soon_threadsafe()类似,call_soon_threadsafe() 是线程安全
- loop.call_later() 延迟多少秒后执行回调函数
- loop.call_at() 在指定时间执行回调函数,这里的时间统一使用 loop.time() 来替代 time.sleep()
- asyncio.run_coroutine_threadsafe() 动态的加入协程,参数为一个回调函数和一个loop对象,返回值为future对象,通过future.result()获取回调函数返回值
3. asyncio启动一个协程
asyncio事件循环可以通过多种不同的方法启动一个协程。一般对于入口函数,最简答的方法就是使用run_until_complete(),并将协程直接传入这个方法。
import asyncio
async def foo():
print("https://www.xtuz.net")
loop = asyncio.get_event_loop()
loop.run_until_complete(foo())
loop.close()
4. asyncio协程获取返回值
run_until_complete 可以获取协程的返回值,如果没有给定返回值,则像函数一样,默认返回None
import asyncio
async def foo():
print("https://www.xtuz.net")
return 'foo'
loop = asyncio.get_event_loop()
ret = loop.run_until_complete(foo())
print(ret)
loop.close()
5. asyncio中协程相互调用
一个协程可以启动另一个协程,从而可以任务根据工作内容,封装到不同的协程中。我们可以在协程中使用await关键字,链式的调度协程,来形成一个协程任务流。
import asyncio
async def foo2(url):
print(url)
return 'foo2'
async def foo():
print("https://www.xtuz.net")
ret = await foo2('https://www.cmypsc.com')
return ret
loop = asyncio.get_event_loop()
ret = loop.run_until_complete(foo())
print(ret)
loop.close()
6. 小结
asyncio提供了完善的异步IO支持;
异步操作需要在coroutine中通过await完成;
多个coroutine可以封装成一组Task然后并发执行。