Python异步编程
异步编程的概念
什么是异步编程
异步编程是一种编程范式,它允许程序在等待某个操作完成时继续执行其他任务,而不是阻塞等待。这种方式特别适合IO密集型任务,如网络请求、文件操作、数据库查询等。
同步 vs 异步
- 同步编程:程序按照顺序执行,每个任务必须等待前一个任务完成才能开始。
- 异步编程:程序可以同时执行多个任务,当一个任务需要等待IO操作时,程序会切换到其他可执行的任务。
异步编程的优势
- 提高程序性能:特别是在IO密集型任务中,可以充分利用CPU资源
- 提高系统吞吐量:可以同时处理更多的请求
- 更好的用户体验:在GUI应用中,不会出现界面卡顿
- 更高效的资源利用:减少线程上下文切换的开销
Python异步编程的历史
- Python 2.2:引入了生成器(generator)
- Python 3.3:引入了
yield from语法,为协程奠定了基础 - Python 3.4:正式引入了
asyncio库 - Python 3.5:引入了
async/await语法,使异步编程更加简洁 - Python 3.6:对
asyncio进行了优化和改进 - Python 3.7:
asyncio成为标准库的稳定部分 - Python 3.8+:继续对
asyncio进行优化和扩展
asyncio库
asyncio简介
asyncio是Python标准库中的异步I/O框架,提供了编写单线程并发代码的基础设施。它使用事件循环(Event Loop)来管理协程(Coroutines)、任务(Tasks)和未来(Futures)。
基本概念
- 事件循环(Event Loop):异步编程的核心,负责管理和调度所有异步任务
- 协程(Coroutine):使用
async def定义的函数,是异步编程的基本单位 - 任务(Task):对协程的封装,用于并发执行协程
- 未来(Future):表示一个异步操作的最终结果
- 回调(Callback):在异步操作完成时调用的函数
第一个asyncio程序
import asyncio
# 定义协程
async def hello():
print("Hello")
# 模拟IO操作,暂停协程执行,切换到其他任务
await asyncio.sleep(1)
print("World")
# 运行协程
async def main():
await hello()
# 获取事件循环并运行
asyncio.run(main())import asyncio
# 定义协程
async def hello():
print("Hello")
# 模拟IO操作,暂停协程执行,切换到其他任务
await asyncio.sleep(1)
print("World")
# 运行协程
async def main():
await hello()
# 获取事件循环并运行
asyncio.run(main())协程(Coroutine)
定义协程
使用async def关键字定义协程函数,协程函数返回一个协程对象。
async def my_coroutine():
return "Hello, Coroutine!"
# 获取协程对象
coro = my_coroutine()async def my_coroutine():
return "Hello, Coroutine!"
# 获取协程对象
coro = my_coroutine()运行协程
协程对象不能直接调用,需要通过事件循环来运行。
# 方法1:使用asyncio.run()(Python 3.7+)
result = asyncio.run(my_coroutine())
print(result) # Hello, Coroutine!
# 方法2:使用事件循环
loop = asyncio.get_event_loop()
result = loop.run_until_complete(my_coroutine())
print(result) # Hello, Coroutine!# 方法1:使用asyncio.run()(Python 3.7+)
result = asyncio.run(my_coroutine())
print(result) # Hello, Coroutine!
# 方法2:使用事件循环
loop = asyncio.get_event_loop()
result = loop.run_until_complete(my_coroutine())
print(result) # Hello, Coroutine!await关键字
await关键字用于等待一个协程完成,它只能在协程函数内部使用。
async def fetch_data():
print("开始获取数据")
await asyncio.sleep(2) # 模拟网络请求
return {"data": "这是获取到的数据"}
async def process_data():
data = await fetch_data() # 等待fetch_data完成
print(f"处理数据: {data}")
return data
asyncio.run(process_data())async def fetch_data():
print("开始获取数据")
await asyncio.sleep(2) # 模拟网络请求
return {"data": "这是获取到的数据"}
async def process_data():
data = await fetch_data() # 等待fetch_data完成
print(f"处理数据: {data}")
return data
asyncio.run(process_data())协程链
可以将多个协程链接起来,形成一个协程链。
async def step1():
await asyncio.sleep(1)
return "Step 1 complete"
async def step2(result1):
await asyncio.sleep(1)
return f"{result1}, Step 2 complete"
async def step3(result2):
await asyncio.sleep(1)
return f"{result2}, Step 3 complete"
async def main():
result1 = await step1()
result2 = await step2(result1)
result3 = await step3(result2)
print(result3) # Step 1 complete, Step 2 complete, Step 3 complete
asyncio.run(main())async def step1():
await asyncio.sleep(1)
return "Step 1 complete"
async def step2(result1):
await asyncio.sleep(1)
return f"{result1}, Step 2 complete"
async def step3(result2):
await asyncio.sleep(1)
return f"{result2}, Step 3 complete"
async def main():
result1 = await step1()
result2 = await step2(result1)
result3 = await step3(result2)
print(result3) # Step 1 complete, Step 2 complete, Step 3 complete
asyncio.run(main())任务(Task)
任务的概念
任务是对协程的封装,用于并发执行协程。当一个协程被封装成任务后,它会被自动调度到事件循环中执行。
创建任务
async def task_func():
await asyncio.sleep(1)
return "Task completed"
async def main():
# 创建任务
task = asyncio.create_task(task_func())
# 可以继续执行其他操作
print("任务已创建")
# 等待任务完成
result = await task
print(f"任务结果: {result}")
asyncio.run(main())async def task_func():
await asyncio.sleep(1)
return "Task completed"
async def main():
# 创建任务
task = asyncio.create_task(task_func())
# 可以继续执行其他操作
print("任务已创建")
# 等待任务完成
result = await task
print(f"任务结果: {result}")
asyncio.run(main())并发执行多个任务
async def task_func(name, delay):
print(f"任务 {name} 开始")
await asyncio.sleep(delay)
print(f"任务 {name} 完成")
return f"任务 {name} 结果"
async def main():
# 创建多个任务
task1 = asyncio.create_task(task_func("A", 2))
task2 = asyncio.create_task(task_func("B", 1))
task3 = asyncio.create_task(task_func("C", 3))
# 等待所有任务完成
results = await asyncio.gather(task1, task2, task3)
print(f"所有任务完成,结果: {results}")
asyncio.run(main())async def task_func(name, delay):
print(f"任务 {name} 开始")
await asyncio.sleep(delay)
print(f"任务 {name} 完成")
return f"任务 {name} 结果"
async def main():
# 创建多个任务
task1 = asyncio.create_task(task_func("A", 2))
task2 = asyncio.create_task(task_func("B", 1))
task3 = asyncio.create_task(task_func("C", 3))
# 等待所有任务完成
results = await asyncio.gather(task1, task2, task3)
print(f"所有任务完成,结果: {results}")
asyncio.run(main())使用as_completed并发执行任务
as_completed函数返回一个迭代器,它会按照任务完成的顺序返回任务结果。
async def task_func(name, delay):
await asyncio.sleep(delay)
return f"任务 {name} 结果"
async def main():
tasks = [
task_func("A", 2),
task_func("B", 1),
task_func("C", 3)
]
for completed_task in asyncio.as_completed(tasks):
result = await completed_task
print(f"获取到结果: {result}")
asyncio.run(main())async def task_func(name, delay):
await asyncio.sleep(delay)
return f"任务 {name} 结果"
async def main():
tasks = [
task_func("A", 2),
task_func("B", 1),
task_func("C", 3)
]
for completed_task in asyncio.as_completed(tasks):
result = await completed_task
print(f"获取到结果: {result}")
asyncio.run(main())Future
Future的概念
Future表示一个异步操作的最终结果,它是一个低层次的对象,通常由asyncio内部使用。任务(Task)是Future的子类。
Future的基本使用
async def main():
# 创建一个Future对象
future = asyncio.Future()
# 在另一个协程中设置Future的结果
async def set_future_result():
await asyncio.sleep(1)
future.set_result("Future completed")
# 启动设置结果的协程
asyncio.create_task(set_future_result())
# 等待Future完成
result = await future
print(f"Future结果: {result}")
asyncio.run(main())async def main():
# 创建一个Future对象
future = asyncio.Future()
# 在另一个协程中设置Future的结果
async def set_future_result():
await asyncio.sleep(1)
future.set_result("Future completed")
# 启动设置结果的协程
asyncio.create_task(set_future_result())
# 等待Future完成
result = await future
print(f"Future结果: {result}")
asyncio.run(main())Future的状态
Future有三种状态:
- Pending:初始状态,表示Future还未完成
- Done:Future已完成,成功或失败
- Cancelled:Future已被取消
async def main():
future = asyncio.Future()
print(f"初始状态: {future}")
print(f"是否已完成: {future.done()}")
# 设置结果
future.set_result("完成")
print(f"设置结果后状态: {future}")
print(f"是否已完成: {future.done()}")
print(f"结果: {future.result()}")
asyncio.run(main())async def main():
future = asyncio.Future()
print(f"初始状态: {future}")
print(f"是否已完成: {future.done()}")
# 设置结果
future.set_result("完成")
print(f"设置结果后状态: {future}")
print(f"是否已完成: {future.done()}")
print(f"结果: {future.result()}")
asyncio.run(main())异步IO操作
异步文件操作
import asyncio
import aiofiles
async def read_file():
async with aiofiles.open("file.txt", "r") as f:
content = await f.read()
print(f"文件内容: {content}")
async def write_file():
async with aiofiles.open("file.txt", "w") as f:
await f.write("Hello, Async File!")
print("文件写入完成")
async def main():
await write_file()
await read_file()
asyncio.run(main())import asyncio
import aiofiles
async def read_file():
async with aiofiles.open("file.txt", "r") as f:
content = await f.read()
print(f"文件内容: {content}")
async def write_file():
async with aiofiles.open("file.txt", "w") as f:
await f.write("Hello, Async File!")
print("文件写入完成")
async def main():
await write_file()
await read_file()
asyncio.run(main())异步网络请求
import asyncio
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as response:
status = response.status
content = await response.text()
print(f"URL: {url}, Status: {status}, Content Length: {len(content)}")
return status
async def main():
urls = [
"https://www.baidu.com",
"https://www.google.com",
"https://www.github.com"
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"所有请求完成,结果: {results}")
asyncio.run(main())import asyncio
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as response:
status = response.status
content = await response.text()
print(f"URL: {url}, Status: {status}, Content Length: {len(content)}")
return status
async def main():
urls = [
"https://www.baidu.com",
"https://www.google.com",
"https://www.github.com"
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"所有请求完成,结果: {results}")
asyncio.run(main())异步上下文管理器
什么是异步上下文管理器
异步上下文管理器是实现了__aenter__和__aexit__方法的对象,用于在异步代码中管理资源。
使用异步上下文管理器
async def main():
# 异步文件操作中的上下文管理器
async with aiofiles.open("file.txt", "r") as f:
content = await f.read()
print(f"文件内容: {content}")
# 异步HTTP请求中的上下文管理器
async with aiohttp.ClientSession() as session:
async with session.get("https://www.baidu.com") as response:
content = await response.text()
print(f"响应内容长度: {len(content)}")
asyncio.run(main())async def main():
# 异步文件操作中的上下文管理器
async with aiofiles.open("file.txt", "r") as f:
content = await f.read()
print(f"文件内容: {content}")
# 异步HTTP请求中的上下文管理器
async with aiohttp.ClientSession() as session:
async with session.get("https://www.baidu.com") as response:
content = await response.text()
print(f"响应内容长度: {len(content)}")
asyncio.run(main())自定义异步上下文管理器
class AsyncContextManager:
async def __aenter__(self):
print("进入异步上下文管理器")
return "上下文管理器返回值"
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("退出异步上下文管理器")
if exc_type:
print(f"发生异常: {exc_type}, {exc_val}")
return False
async def main():
async with AsyncContextManager() as cm:
print(f"在上下文管理器中,获取到的值: {cm}")
asyncio.run(main())class AsyncContextManager:
async def __aenter__(self):
print("进入异步上下文管理器")
return "上下文管理器返回值"
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("退出异步上下文管理器")
if exc_type:
print(f"发生异常: {exc_type}, {exc_val}")
return False
async def main():
async with AsyncContextManager() as cm:
print(f"在上下文管理器中,获取到的值: {cm}")
asyncio.run(main())异步迭代器
什么是异步迭代器
异步迭代器是实现了__aiter__和__anext__方法的对象,用于在异步代码中进行迭代。
使用异步迭代器
async def async_generator():
for i in range(5):
await asyncio.sleep(0.5)
yield i
async def main():
# 使用异步for循环
async for i in async_generator():
print(f"异步迭代器返回值: {i}")
asyncio.run(main())async def async_generator():
for i in range(5):
await asyncio.sleep(0.5)
yield i
async def main():
# 使用异步for循环
async for i in async_generator():
print(f"异步迭代器返回值: {i}")
asyncio.run(main())自定义异步迭代器
class AsyncIterator:
def __init__(self, max_count):
self.max_count = max_count
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count < self.max_count:
await asyncio.sleep(0.5)
self.count += 1
return self.count
else:
raise StopAsyncIteration
async def main():
async for i in AsyncIterator(5):
print(f"自定义异步迭代器返回值: {i}")
asyncio.run(main())class AsyncIterator:
def __init__(self, max_count):
self.max_count = max_count
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count < self.max_count:
await asyncio.sleep(0.5)
self.count += 1
return self.count
else:
raise StopAsyncIteration
async def main():
async for i in AsyncIterator(5):
print(f"自定义异步迭代器返回值: {i}")
asyncio.run(main())异步编程的最佳实践
1. 使用async/await语法
优先使用async/await语法,而不是旧的生成器语法,这样代码更简洁、易读。
2. 避免阻塞操作
在异步代码中,避免使用阻塞操作,如:
- 同步文件操作(使用
aiofiles替代) - 同步网络请求(使用
aiohttp替代) - CPU密集型操作(考虑使用
loop.run_in_executor)
3. 使用asyncio.gather并发执行任务
当需要并发执行多个任务时,使用asyncio.gather函数,它会等待所有任务完成并返回结果。
4. 使用异步上下文管理器
在处理资源时,使用异步上下文管理器,确保资源被正确释放。
5. 合理设置超时
使用asyncio.wait_for函数为异步操作设置超时,避免任务无限期等待。
async def long_running_task():
await asyncio.sleep(5)
return "完成"
async def main():
try:
result = await asyncio.wait_for(long_running_task(), timeout=2)
print(f"任务完成,结果: {result}")
except asyncio.TimeoutError:
print("任务超时")
asyncio.run(main())async def long_running_task():
await asyncio.sleep(5)
return "完成"
async def main():
try:
result = await asyncio.wait_for(long_running_task(), timeout=2)
print(f"任务完成,结果: {result}")
except asyncio.TimeoutError:
print("任务超时")
asyncio.run(main())6. 使用异步队列进行任务调度
当需要处理大量任务时,使用asyncio.Queue进行任务调度。
async def worker(queue, worker_id):
while True:
task = await queue.get()
print(f"工作者 {worker_id} 开始处理任务 {task}")
await asyncio.sleep(1)
print(f"工作者 {worker_id} 完成任务 {task}")
queue.task_done()
async def main():
# 创建队列
queue = asyncio.Queue()
# 创建工作者
workers = [asyncio.create_task(worker(queue, i)) for i in range(3)]
# 添加任务到队列
for i in range(10):
await queue.put(i)
# 等待所有任务完成
await queue.join()
# 取消工作者
for w in workers:
w.cancel()
# 等待工作者取消
await asyncio.gather(*workers, return_exceptions=True)
print("所有任务完成")
asyncio.run(main())async def worker(queue, worker_id):
while True:
task = await queue.get()
print(f"工作者 {worker_id} 开始处理任务 {task}")
await asyncio.sleep(1)
print(f"工作者 {worker_id} 完成任务 {task}")
queue.task_done()
async def main():
# 创建队列
queue = asyncio.Queue()
# 创建工作者
workers = [asyncio.create_task(worker(queue, i)) for i in range(3)]
# 添加任务到队列
for i in range(10):
await queue.put(i)
# 等待所有任务完成
await queue.join()
# 取消工作者
for w in workers:
w.cancel()
# 等待工作者取消
await asyncio.gather(*workers, return_exceptions=True)
print("所有任务完成")
asyncio.run(main())常见问题和解决方案
1. 如何处理CPU密集型任务
CPU密集型任务会阻塞事件循环,解决方案是使用loop.run_in_executor将CPU密集型任务放到线程池或进程池中执行。
import asyncio
import concurrent.futures
def cpu_bound_task(n):
# 模拟CPU密集型任务
result = 0
for i in range(n):
result += i * i
return result
async def main():
loop = asyncio.get_running_loop()
# 使用线程池执行CPU密集型任务
with concurrent.futures.ThreadPoolExecutor() as executor:
result = await loop.run_in_executor(executor, cpu_bound_task, 10000000)
print(f"CPU密集型任务结果: {result}")
# 使用进程池执行CPU密集型任务
with concurrent.futures.ProcessPoolExecutor() as executor:
result = await loop.run_in_executor(executor, cpu_bound_task, 10000000)
print(f"CPU密集型任务结果: {result}")
asyncio.run(main())import asyncio
import concurrent.futures
def cpu_bound_task(n):
# 模拟CPU密集型任务
result = 0
for i in range(n):
result += i * i
return result
async def main():
loop = asyncio.get_running_loop()
# 使用线程池执行CPU密集型任务
with concurrent.futures.ThreadPoolExecutor() as executor:
result = await loop.run_in_executor(executor, cpu_bound_task, 10000000)
print(f"CPU密集型任务结果: {result}")
# 使用进程池执行CPU密集型任务
with concurrent.futures.ProcessPoolExecutor() as executor:
result = await loop.run_in_executor(executor, cpu_bound_task, 10000000)
print(f"CPU密集型任务结果: {result}")
asyncio.run(main())2. 如何调试异步代码
异步代码的调试比同步代码复杂,可以使用以下方法:
- 使用
asyncio.run()函数运行协程,它会自动处理异常 - 使用
asyncio.debug模式运行事件循环 - 使用
logging模块记录异步操作的状态 - 使用IDE的调试工具,如PyCharm的异步调试功能
import asyncio
import logging
# 启用asyncio调试日志
logging.basicConfig(level=logging.DEBUG)
async def main():
await asyncio.sleep(1)
print("调试完成")
# 启用调试模式
asyncio.run(main(), debug=True)import asyncio
import logging
# 启用asyncio调试日志
logging.basicConfig(level=logging.DEBUG)
async def main():
await asyncio.sleep(1)
print("调试完成")
# 启用调试模式
asyncio.run(main(), debug=True)3. 如何处理异常
在异步代码中处理异常的方式与同步代码类似,可以使用try/except语句。
async def task_with_exception():
await asyncio.sleep(1)
raise ValueError("任务异常")
async def main():
try:
await task_with_exception()
except ValueError as e:
print(f"捕获到异常: {e}")
asyncio.run(main())async def task_with_exception():
await asyncio.sleep(1)
raise ValueError("任务异常")
async def main():
try:
await task_with_exception()
except ValueError as e:
print(f"捕获到异常: {e}")
asyncio.run(main())7. 使用 TaskGroup 管理任务群组(Python 3.11+)
# TaskGroup 自动管理任务生命周期,替代手动 gather
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(task_func("A", 2))
task2 = tg.create_task(task_func("B", 1))
task3 = tg.create_task(task_func("C", 3))
# 所有任务在退出 TaskGroup 时自动完成
print("所有任务完成")
# TaskGroup vs gather:
# - 自动处理异常传播,任一任务失败则取消其他
# - 不需要手动 cancel 和 return_exceptions# TaskGroup 自动管理任务生命周期,替代手动 gather
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(task_func("A", 2))
task2 = tg.create_task(task_func("B", 1))
task3 = tg.create_task(task_func("C", 3))
# 所有任务在退出 TaskGroup 时自动完成
print("所有任务完成")
# TaskGroup vs gather:
# - 自动处理异常传播,任一任务失败则取消其他
# - 不需要手动 cancel 和 return_exceptions8. 使用 timeout 上下文管理器(Python 3.11+)
# 3.11+ 的 asyncio.timeout 更直观
async def main():
try:
async with asyncio.timeout(2):
await long_running_task()
except TimeoutError:
print("操作超时")# 3.11+ 的 asyncio.timeout 更直观
async def main():
try:
async with asyncio.timeout(2):
await long_running_task()
except TimeoutError:
print("操作超时")9. 使用 Barrier 同步(Python 3.11+)
# Barrier 让多个协程在指定点同步
async def worker(name, barrier):
await asyncio.sleep(random.uniform(0.5, 2))
print(f"{name} 到达屏障")
await barrier.wait()
print(f"{name} 通过屏障")
async def main():
barrier = asyncio.Barrier(3)
async with asyncio.TaskGroup() as tg:
tg.create_task(worker("A", barrier))
tg.create_task(worker("B", barrier))
tg.create_task(worker("C", barrier))# Barrier 让多个协程在指定点同步
async def worker(name, barrier):
await asyncio.sleep(random.uniform(0.5, 2))
print(f"{name} 到达屏障")
await barrier.wait()
print(f"{name} 通过屏障")
async def main():
barrier = asyncio.Barrier(3)
async with asyncio.TaskGroup() as tg:
tg.create_task(worker("A", barrier))
tg.create_task(worker("B", barrier))
tg.create_task(worker("C", barrier))异步编程框架
除了asyncio,Python还有一些其他的异步编程框架:
- Tornado:一个Web框架和异步网络库
- Twisted:一个事件驱动的网络编程框架
- uvloop:一个基于libuv的高性能事件循环,兼容asyncio
- curio:一个基于Python生成器的异步编程库
- trio:一个专注于易用性和正确性的异步编程库
总结
异步编程是Python中一个重要的概念,特别是在处理IO密集型任务时。通过使用asyncio库和async/await语法,我们可以编写高效、简洁的异步代码。
异步编程的核心概念包括:
- 事件循环
- 协程
- 任务
- Future
- 异步上下文管理器
- 异步迭代器
在实际应用中,我们需要注意:
- 避免在异步代码中使用阻塞操作
- 合理使用并发执行任务
- 正确处理异常
- 遵循异步编程的最佳实践
随着Python的不断发展,异步编程将在Python生态系统中扮演越来越重要的角色,特别是在Web开发、网络编程、数据处理等领域。
Cao Note