协程专题
定义和概念
协程coroutine
又叫做微线程,在统一线程里,不同子程序可以中断执行其他子程序。中断后,可以从中断的地方继续执行。协程拥有自己的寄存器上下文和栈。用async定义的一个函数,调用时候不会立即执行,而是会返回一个协程对象。python3.7之前协程对象需要注册到事件循环,由事件循环进行调用。python3.7之后协程对象直接使用async.run运行。
event_loop事件循环
async中开启一个无限的事件循环,asyncio会自动在满足条件时调用协程对象,需要将协程对象注册到该事件循环上即可。python3.7之后不建议显式使用事件循环。
task对象
一个协程对象就是一个原生可以挂起的函数,任务是对协程的进一步封装,包含任务的各种状态。 task对象是future对象的子类,将coroutine和future封装在一起,同时也有异常处理等功能。(future对象代表没有执行任务的结果)
async关键字
python3.5之后用于定义协程的关键字,async def定义协程。同时也可以定义异步迭代器和异步上下文管理器
await关键字:作用1、用于挂起协程,await后面跟的是耗时操作,表示此处将调度权释放给事件循环。2、用于返回协程执行结果 ** 注意:1、await只能修饰coroutine和task对象。2、await语句只能位于协程内部,不能位于同步程序中。3、await修饰coroutine会造成阻塞,所以更建议修饰task对象**
基本协程调用
python3.5-3.6 旧版调用方式
1、定义协程对象:async def hello(x)…
2、事件循环: loop = asyncio.get_event_loop() #定义事件循环
3、将协程转为task:asyncio.ensure_future(hello(i)) 或者asyncio.create_task(hello(i))
4、将task放入事件循环对象中(以下两句等价)
loop.run_until_complete(asyncio.gather(*tasks)
loop.run_until_complete(asyncio.wait(tasks))
5、结果通过Task的result()方法获取
python3.7以上调用方式:
1、定义协程对象:async def hello(x)…
2、定义外部协程对象 async def main() 内部包含1(并且把1包装成task)
3、asyncio.run(mian)
4、结果通过await 获取
import time
import asyncio
import random
sentences = [
'驿外断桥边,寂寞开无主,已是黄昏独自愁',
'十年生死两茫茫',
'千里江陵一日还',
'两岸猿声啼不住,轻舟已过万重山'
]
async def work(index):
print('start async {}'.format(index))
sentence = sentences[index]
for s in sentence:
await asyncio.sleep(0.3)
return sentence
#python3.6
tasks = [asyncio.ensure_future(work(index)) for index in range(3)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) #如果是任务list,则使用wait方法,单个任务可以直接run_until_complete
for t in tasks:
print(t.result())
#python3.7
# async def main():
# tasks = [asyncio.create_task(work(index)) for index in range(3)]
# await asyncio.wait(tasks)
# for t in tasks:
# print(t.result())
# print('end')
# asyncio.run(main()) #aysncio.run方法接收并执行一个协程(coroutine)对象,而非Task或Future对象
函数说明
async.run()
必须传入一个协程,会自动把这个协程包装task并启动事件循环。多个协程可以都包装在一个最外层的协程中,使用async.run启动。async.run是python3.7新的协程启动方式。对比asyncio.get_event_loop() + loop.run_until_complete() 方式,可以不用管事件循环的启动和释放。例子见上【python3.7以上调用方式】
asyncio.create_task()
将协程包装成task对象,其功能和asyncio.ensure_future()一样。python3.7之后建议使用asyncio.create_task()
例子见上【python3.7以上调用方式】
asyncio.get_running_loop() 和 loop.run_in_executor()
两者配合可以把一个同步函数包装成一个协程加入线程池,从而加入事件循环,同步函数因为会单独开线程,所以不会阻塞事件循环,示例如下:
import asyncio
import concurrent.futures
import time
def blocking_function(seconds):
print(f"Starting blocking function for {seconds} seconds.")
time.sleep(seconds)
return f"Done after {seconds} seconds."
async def main():
# 获取当前事件循环
loop = asyncio.get_running_loop()
# 创建线程池执行器
with concurrent.futures.ThreadPoolExecutor() as executor:
# 使用run_in_executor异步执行阻塞操作
task1 = loop.run_in_executor(executor, blocking_function, 2)
task2 = loop.run_in_executor(executor, blocking_function, 4)
# 等待两个任务完成
result1 = await task1
result2 = await task2
print(result1)
print(result2)
asyncio.run(main())
asyncio.wait()
async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED)
其本身也是一个协程,且为耗时操作,所以必须用await修饰。主要作用是等待一个可迭代对象(通常是一组 Future 或 Task 对象)中的某些任务完成。这个函数提供了更灵活的控制来管理并发执行的异步任务
具体来说,asyncio.wait 允许你指定以下参数:
fs: 一个可迭代对象,包含了你想要等待完成的 Future 或 Task 对象。
timeout: 可选参数,等待的超时时间(秒),如果设置,则在指定时间后不论任务是否完成都会结束等待。
return_when: 决定了在什么条件下返回。它可以是 asyncio.FIRST_COMPLETED(只要有一个任务完成就返回)、asyncio.ALL_COMPLETED(默认值,所有任务都完成才返回)或 asyncio.FIRST_EXCEPTION(只要有一个任务抛出异常就返回)。
使用 asyncio.wait 后,它会返回两个集合并发结果:一个是已完成的任务集合,另一个是未完成的任务集合。这使得你可以根据任务的状态来决定下一步的操作,比如继续等待、取消未完成的任务或者处理已完成任务的结果。
# python3.7
async def main():
tasks = [asyncio.create_task(work(index)) for index in range(3)]
done, pending = await asyncio.wait(tasks) # 等待所有任务执行完成,返回结果列表
for item in done:
print(item.result()) # 结果是乱序的
print('end')
asyncio.run(main())
asyncio.gather()
它用于并发执行多个异步操作(协程),并能够收集这些操作的结果。和asyncio.wait()区别是输入需要进行解包,以及返回结果是有序的。
def gather(*coros_or_futures, return_exceptions=False)
coros_or_futures: 如果你有多个协程要执行,可以直接将它们作为参数传递,或者通过解包操作符 * 传递一个包含协程的列表或元组。
return_exceptions(可选): 一个布尔值,默认为 False。如果设置为 True,则在遇到任何协程抛出异常时,不会立即停止执行其他协程,而是将异常作为结果的一部分收集起来。
示例如下
async def main():
tasks = [asyncio.create_task(work(index)) for index in range(3)]
res = await asyncio.gather(*tasks) # 等待所有任务执行完成,返回结果列表,比wait方法更简洁,比await更高效
print(res)
print('end')
asyncio.run(main())
回调函数
在上面示例里面加上两个步骤,即可在异步函数调用结束后调用回调函数:
1、回调函数定义callback
2、task.add_done_callback(callback)
import asyncio,time
async def hello(x):
await asyncio.sleep(x) #time.sleep()是同步方法,不支持异步调用,此处使用asyncio.sleep
return '暂停了{}s'.format(x), x
def callback(future:asyncio.Task):
sum = future.result()[-1] + 3
print(sum)
if __name__ == '__main__':
time_start = time.time()
tasks = []
loop = asyncio.get_event_loop()
for i in range(3):
#task = asyncio.create_task(hello(i))
task = asyncio.ensure_future(hello(i)) #ensure_future 和 create_task 都可以,优先用 ensure_future
task.add_done_callback(callback)
tasks.append(task)
#loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.gather(*tasks)) #gather 和 wait 功能类似, wait接受的是列表,gather直接接受对象
for task in tasks:
print(task.result())
print(time.time() - time_start)
异步迭代器
允许你在异步环境下进行迭代操作。异步迭代器主要通过实现__aiter__()和__anext__()这两个魔法方法来工作,分别用于初始化迭代器和获取下一个异步的迭代项。这使得你能够在协程中使用async for语句遍历异步数据源,比如从网络流式读取数据或者处理异步生成的数据序列,而无需阻塞事件循环。注意:异步迭代器必须在协程中定义,不能出现在同步函数中。 异步迭代器中往往也存在耗时异步操作
import asyncio
class AsyncCounter:
"""
异步迭代器示例,每隔一段时间生成下一个计数。
"""
def __init__(self, limit=10, delay=1):
self.limit = limit
self.delay = delay
self.current = 0
async def __aiter__(self):
while self.current < self.limit:
await asyncio.sleep(self.delay) # 模拟异步延迟
yield self.current
self.current += 1
async def async_generator():
for i in range(5):
# 模拟异步操作,如网络请求或磁盘I/O,这里使用sleep模拟延迟
await asyncio.sleep(1)
yield i
async def main():
#counter = AsyncCounter(limit=5, delay=2) # 生成5个数字,每次间隔2秒
counter = async_generator()
async for number in counter:
print(f"Count: {number}")
# 运行主函数
asyncio.run(main())
循环读文件
import asyncio
import aiofiles
async def async_file_reader(file_path):
"""
用于逐行读取文件。
"""
async with aiofiles.open(file_path, mode='r') as file:
async for line in file:
# 模拟异步读取每行的延迟
await asyncio.sleep(0.1) # 假设每读一行等待0.1秒
yield line.strip()
async def main():
file_path = 'example.txt' # 假定要读取的文件路径
async for line in async_file_reader(file_path):
print(line)
asyncio.run(main())
异步上下文管理器
与同步上下文管理器类似,但是设计用于异步环境中。异步上下文管理器通过定义两个特殊的方法__aenter__和__aexit__来工作,这些方法会在async with语句的入口和出口处被调用,分别用于获取资源和释放资源,同时也能处理在async with块中发生的异常。
import asyncio
class AsyncFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
async def __aenter__(self):
print(f"Opening file {self.filename}...")
# 模拟异步打开文件,这里简化处理,实际应用中可能是网络请求等
await asyncio.sleep(1)
self.file = open(self.filename, self.mode)
return self.file
async def __aexit__(self, exc_type, exc, tb):
print("Closing file.")
if self.file:
self.file.close()
# 可以在这里根据exc_type等参数处理异常
# 如果没有异常,这三个参数将为None, None, None
async def read_file_contents(filename):
async with AsyncFile(filename, 'r') as f:
contents = await f.read()
print(contents)
asyncio.run(read_file_contents('example.txt'))
协程和线程对比示例
分别使用协程和线程创建多文件
import asyncio, time
async def write_file(path, content:str, index:int):
print("now writing file", index)
with open(path, 'w') as f:
f.write("this is the {} file: {}".format(index,content))
import threading, time
def write_file2(path, content:str, index:int):
print("now writing file", index)
with open(path, 'w') as f:
f.write("this is the {} file: {}".format(index,content))
if __name__ == '__main__':
time_start = time.time()
loop = asyncio.get_event_loop()
tasks = []
for i in range(300):
#task = asyncio.create_task(write_file("test_{}.txt".format(i), "the data is {}".format(i), i))
task = asyncio.ensure_future(write_file("tmp/test_{}.txt".format(i), "the data is {}".format(i),i))
tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))
print("async time cost:", time.time() - time_start)
time_start = time.time()
threads = []
for i in range(300):
threads.append(threading.Thread(target=write_file2, args=("tmp1/test_{}.txt".format(i), "the data is {}".format(i),i)))
[t.start() for t in threads]
[t.join() for t in threads]
print("thread time cost:", time.time() - time_start)
协程速度和多线程接近。