异步编程 asyncio模块
1 事件循环
事件循环本质是循环,负责不断地监测并执行特定的代码。
任务列表 = [任务1, 任务2, 任务3...]
while True:
1. 两个列表:就绪任务列表 和 已完成的任务列表
2. 遍历检查任务列表中的每一个任务的状态,
任务状态分为可执行,不可执行,已完成
将可执行的任务放入可执行的任务列表,
将已完成的任务放入已完成的任务列表,
如果任务是不可执行的,说明这个任务此时可能遇到IO等阻塞操作,处于等待状态,本次循环不处理不可执行的任务。
for 可执行的任务 in 可执行的任务列表:
执行可执行的任务
for 已完成的任务 in 已完成的任务列表:
从任务列表中移除已完成的任务
if 任务列表为空(说明所有任务均已完成):
终止循环
import asyncio
# 获取一个事件循环
event_loop = asyncio.get_event_loop()
# 将任务放入任务列表中,并通过事件循环监测并执行任务
event_loop.run_until_complete(asyncio.wait(task_list))
2 快速上手
协程函数:使用async def 语句定义的函数。
协程对象:执行协程函数得到的是协程对象。
# 协程函数
async def func():
pass
# 协程对象
coroutine_obj = func()
# sys:1: RuntimeWarning: coroutine 'func' was never awaited
执行协程函数时,协程函数内部代码不会执行,只是获取一个协程对象,必须与事件循环配合使用,即将协程对象交给事件循环处理。
import asyncio
async def func():
print(123)
coroutine_obj = func()
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(coroutine_obj)
python3.7 简便写法
import asyncio
async def func():
print(123)
coroutine_obj = func()
asyncio.run(coroutine_obj)
3 await关键字
await = async + wait
await + 可等待的对象
可等待的对象包括 协程对象、Task对象 和 Future对象。
await就是等待后面的对象有了结果后,才继续执行。
在等待期间,线程会切换并执行其它任务,当等待结束时,这个任务的状态变为可执行,事件循环会在下一次循环时检测任务状态并继续执行这个任务。
import asyncio
async def func():
print(123)
res = await asyncio.sleep(1)
print(res) # None
asyncio.run(func())
import asyncio
async def func1():
print('func1 start.')
await asyncio.sleep(1)
print('func1 end.')
return 'func1'
async def func2():
print('func2 start.')
res1 = await func1()
print(res1)
res2 = await func1()
print(res2)
print('func2 end.')
asyncio.run(func2())
'''
func2 start.
func1 start.
func1 end.
func1
func1 start.
func1 end.
func1
func2 end.
'''
4 Task对象
Tasks are used to schedule coroutines concurrently.
When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon.
Tasks用于并发地调度当前的协程,可以简单地理解为Task对象用于立即将任务放入事件循环中,或者是在事件循环中并发地创建多个任务。
通过使用 asyncio.create_task(协程对象) 函数创建Task对象,并将任务立即添加到事件循环中,等待被调度执行。
注意,这里的并发实质上是串行,通过在一个线程中快速切换任务达到了并发的效果。
创建Task对象除了使用asyncio.create_task(协程对象) 函数(Python 3.7),还可以使用底层级的函数 loop.create_task() 和 ensure_future()。
不建议手动进行Task对象的实例化。
import asyncio
async def func():
print('func start.')
await asyncio.sleep(1)
print('func end.')
return 'func'
async def main():
print('main start.')
# 创建Task对象,并将任务func添加到事件循环中。
task1 = asyncio.create_task(func())
task2 = asyncio.create_task(func())
# 目前事件循环中存在三个任务,主任务main,task1和task2
print('main end.')
res1 = await task1
res2 = await task2
print(res1, res2)
asyncio.run(main()) # 将任务main添加到事件循环中
'''
main start.
main end.
func start.
func start.
func end.
func end.
func func
'''
先执行主任务main,当运行到 res1 = await task1 时,等待任务task1,执行子任务task1,当运行到func中的 await asyncio.sleep(1) 时,切换任务列表中的另一个任务,此时主任务main必须等待子任务task1返回结果才能继续执行,因此执行的是任务列表中的另一个子任务task2,当运行到func中的 await asyncio.sleep(1) 时,继续切换任务,直到子任务task1和task2执行结束获得返回结果,主任务继续执行。
import asyncio
async def func():
print('func start.')
await asyncio.sleep(1)
print('func end.')
return 'func'
async def main():
print('main start.')
task1 = asyncio.create_task(func())
task2 = asyncio.create_task(func(), name='自定义名字Task')
task_list = [task1, task2, ]
print('main end.')
done, pending = await asyncio.wait(task_list)
# 可以为所有任务设置超时时间
# done, pending = await asyncio.wait(task_list, timeout=1)
# done是个集合,包含了task_list中所有的Task对象以及结果。
print(done)
# {<Task finished name='Task-2' coro=<func() done, defined at D:/demo.py:25> result='func'>, <Task finished name='自定义名字Task' coro=<func() done, defined at D:/demo.py:25> result='func'>}
# 如果设置了timeout,执行超时的话,done集合为空,pending集合则包含了未完成的Task对象。
print(pending) # set()
# event_loop = asyncio.get_event_loop()
# event_loop.run_until_complete(coroutine_obj)
asyncio.run(main())
报错代码
import asyncio
async def func():
print('func start.')
await asyncio.sleep(1)
print('func end.')
return 'func'
task_list = [
asyncio.create_task(func(), name='T1'), # Task对象
# sys:1: RuntimeWarning: coroutine 'func' was never awaited
asyncio.create_task(func(), name='T2'),
]
done, padding = asyncio.run(asyncio.wait(task_list))
函数asyncio.create_task会创建Task对象并将其立即添加到事件循环中,创建事件循环是在函数asyncio.run中完成的,执行函数asyncio.create_task时不存在事件循环,因此报错。
import asyncio
async def func():
print('func start.')
await asyncio.sleep(1)
print('func end.')
return 'func'
task_list = [
func(), # 协程对象
func(),
]
# asyncio.run会在生成事件循环后自动根据task_list中的协程对象创建Task对象。
done, padding = asyncio.run(asyncio.wait(task_list))
5 asyncio.Future对象
模块asyncio的Future类是Task类的基类。
A Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation.
模块asyncio中的Future相当于底层的接口,用于等待异步处理的结果。
Task类的方法await是基于Future类中的一个属性_state,它用于存储维护任务的状态。
import asyncio
async def main():
loop = asyncio.get_running_loop() # 获取当前的事件循环
fut = loop.create_future() # 创建Future对象,但未绑定任何行为,因此该任务不会有结果。
await fut # 等待Future对象的任务结果,但始终没有结果,因此会一直等待。
asyncio.run(main())
import asyncio
async def set_after(fut):
await asyncio.sleep(1)
fut.set_result('123')
async def main():
loop = asyncio.get_running_loop()
fut = loop.create_future()
# 创建Task对象,绑定了函数set_after,其参数是Future对象。
# 函数set_after会为Future对象手动设置结果,因此Future对象的任务有了执行结果。
await loop.create_task(set_after(fut))
# 此时Future对象的任务有执行结果,可以结束等待。
res = await fut
print(res) # 123
asyncio.run(main())
6 concurrent.futures.Future对象
模块asyncio的Future对象是在使用进程池/线程池实现异步效果的时候使用的。
import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures.process import ProcessPoolExecutor
def func(val):
time.sleep(1)
print(val)
# 创建线程池
pool = ThreadPoolExecutor(max_workers=5)
# 创建进程池
# pool = ProcessPoolExecutor(max_workers=5)
for i in range(10):
fut = pool.submit(func, i)
print(fut)
协程与进程池和线程池交叉使用
使用场景:使用某个第三方模块,而这个模块不支持协程,此时需要包装。
- 使用默认的线程池
import time
import asyncio
import concurrent.futures
def func1():
time.sleep(1)
return 'func1'
async def main():
loop = asyncio.get_running_loop()
# 1. 创建一个线程池;
# 2. 调用ThreadPoolExector的方法submit,在线程池中申请一个线程用于执行函数func1,
# 返回一个concurrent.futures.Future对象;
# 3. 调用函数asyncio.wrap_future,将concurrent.futures.Future对象包装为asyncio.Future对象;
# 因为concurrent.futures.Future对象不支持await语法,所以需要将其包装为asyncio.Future对象。
fut = loop.run_in_executor(None, func1) # 参数None表示使用默认的线程池。
res = await fut
print(f'Default thread pool {res}') # Default thread pool func1
asyncio.run(main())
- 使用自定义的线程池
import time
import asyncio
import concurrent.futures
def func1():
time.sleep(1)
return 'func1'
async def main():
loop = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
res = await loop.run_in_executor(pool, func1)
print(f'Custom thread {res}') # Custom thread func1
asyncio.run(main())
- 使用自定义的进程池
import time
import asyncio
import concurrent.futures
def func1():
time.sleep(1)
return 'func1'
async def main():
loop = asyncio.get_running_loop()
with concurrent.futures.ProcessPoolExecutor() as pool:
res = await loop.run_in_executor(pool, func1)
print(f'Custom process {res}') # Custom process func1
if __name__ == '__main__':
asyncio.run(main())
7 案例 asyncio + 不支持异步的模块
import asyncio
import requests
async def download_image(url):
print('开始下载。')
loop = asyncio.get_event_loop()
# requests模块默认不支持异步操作,可以配合线程池实现异步操作
# 会创建多个线程
fut = loop.run_in_executor(None, requests.get, url)
resp = await fut
print('下载完成。')
file_name = url.rsplit('/')[-1]
with open(file_name, mode='wb') as file_obj:
file_obj.write(resp.content)
if __name__ == '__main__':
url_list = [
'http://up.deskcity.org/pic_source/d7/90/03/d790030b9ba42ba6e65f120cbdc3fd92.jpg',
'http://vg01.oss-cn-hangzhou.aliyuncs.com/photo/web/160907133030907.jpg',
]
task_list = [download_image(each_url) for each_url in url_list]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(task_list))
8 异步迭代器
迭代器:实现了方法__iter__()和__next__()的对象。
可迭代对象:实现了内置有__iter__()方法的对象。
异步迭代器:实现了方法__aiter__()和__anext__()的对象,aiter__必须返回一个awaitable对象,async for会处理异步迭代器的方法__anext()所返回的可等待对象,直到其引发了一个StopAsyncIteration异常。
异步可迭代对象:可在async for语句中被使用的对象,通过它的方法__aiter__()可以返回一个异步迭代器(asynchronous iterator)。
import asyncio
# 自定义异步迭代器
class Reader(object):
def __init__(self):
self.count = 0
async def readline(self):
# await asyncio.sleep(1)
self.count += 1
if self.count == 100:
return None
return self.count
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val is None:
raise StopAsyncIteration
return val
async def func():
obj = Reader()
# async for需要处于协程函数内
async for item in obj:
print(item)
9 异步上下文管理器
通过定义方法__aenter__()和__aexit__()对async with语句中的环境进行控制。
import asyncio
class AsyncContextManager:
def __init__(self):
self.conn = None
async def do_sth(self):
# 异步操作数据库
return 123
async def __aenter__(self):
# 异步连接数据库
self.conn = await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc, tb):
# 异步关闭数据库连接
await asyncio.sleep(1)
async def func():
obj = AsyncContextManager()
# async with需要处于协程函数内
# 进入异步上下文管理器时先执行方法__aenter__,
# f是方法__aenter__的返回结果。
# 离开异步上下文管理器时执行方法__aexit__。
async with obj as f:
res = await f.do_sth()
print(res)
asyncio.run(func())
10 事件循环升级 uvloop
uvloop是asyncio中的事件循环的替代方案,大幅度提高事件循环效率和性能。
import asyncio
import uvloop
# 将asyncio中的事件循环替换为uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 事件循环会自动使用uvloop
asyncio.run()