10. 协程编程#
在Python中,async和await是用于定义和调用协程的关键字,它们是Python 3.5及以上版本中引入的异步编程特性的一部分。异步编程允许你编写非阻塞的代码,这对于I/O密集型和高延迟的操作(如网络请求、文件读写等)非常有用。
10.1. async关键字#
async用于定义一个协程。它可以放在一个函数定义前,使得该函数成为一个异步函数(也称为协程)。异步函数在被调用时不会立即执行,而是返回一个协程对象,该对象需要被await或在事件循环中执行。
定义异步函数的示例:
async def fetch_data():
await asyncio.sleep(1) # 模拟一个异步操作
return {'data': 1}
10.2. await关键字#
await用于暂停执行一个协程,直到一个可等待对象(如协程、任务或者Future)完成。它只能在异步函数内部使用。 使用await的示例:
async def main():
data = await fetch_data() # 等待fetch_data协程完成
print(data)
在这个例子中,main函数中的await fetch_data()会暂停main的执行,直到fetch_data协程完成并返回结果。
完整的异步编程示例
import asyncio
async def fetch_data():
print("开始获取数据...")
await asyncio.sleep(2) # 模拟一个耗时的异步操作
print("数据获取完成")
return {'data': 1}
async def main():
print("开始执行")
data = await fetch_data() # 等待fetch_data协程完成
print("获取到的数据:", data)
# 运行事件循环
asyncio.run(main())
上面代码不能运行在jupyter中。
在这个例子中,asyncio.run(main())会启动事件循环,执行main协程。main协程内部等待fetch_data协程完成,并在完成后继续执行。 注意事项
只能在异步函数内部使用await:如果你尝试在普通函数中使用await,会引发语法错误。
async定义的函数返回协程对象:当你定义一个异步函数并调用它时,它不会立即执行,而是返回一个协程对象,你需要使用await来启动它,或者在事件循环中运行。
事件循环:异步编程需要一个事件循环来驱动协程的执行。asyncio.run()是一个方便的函数,它创建一个新的事件循环,运行传入的协程,然后关闭事件循环。
错误处理:在异步函数中,你可以使用try/except块来捕获异常,就像在同步代码中一样。
协程的组合:可以使用asyncio.gather()来并发运行多个协程。
异步编程是一个复杂的主题,涉及到事件循环、任务、Future对象等多个概念。掌握这些概念可以帮助你编写更高效、更可读的异步代码。
10.3. asyncio包#
asyncio
是 Python 的标准库之一,用于编写单线程的并发代码,使用 async/await
语法。它允许你以异步的方式执行多个操作,这在处理 I/O 密集型任务时非常有用,比如网络请求、文件读写等。以下是一些 asyncio
的常用用法和示例:
10.3.1. 1. 运行协程#
最基本的用法是使用 asyncio.run()
来运行一个协程:
import asyncio
async def main():
print('Hello')
await asyncio.sleep(1)
print('World')
asyncio.run(main())
10.3.2. 2. 创建和等待协程任务#
你可以创建协程任务并使用 asyncio.wait()
来等待它们完成:
import asyncio
async def coro(name):
print(f'Task {name} started')
await asyncio.sleep(1)
print(f'Task {name} finished')
async def main():
task1 = asyncio.create_task(coro('A'))
task2 = asyncio.create_task(coro('B'))
await asyncio.wait([task1, task2])
asyncio.run(main())
10.3.3. 3. 并发运行多个协程#
使用 asyncio.gather()
来并发运行多个协程:
import asyncio
async def coro(name):
print(f'Task {name} started')
await asyncio.sleep(1)
print(f'Task {name} finished')
async def main():
await asyncio.gather(
coro('A'),
coro('B'),
coro('C')
)
asyncio.run(main())
10.3.4. 4. 超时控制#
使用 asyncio.wait_for()
来设置协程的超时时间:
import asyncio
async def coro(name):
print(f'Task {name} started')
await asyncio.sleep(2)
print(f'Task {name} finished')
async def main():
try:
await asyncio.wait_for(coro('A'), timeout=1)
except asyncio.TimeoutError:
print('Task A timed out')
asyncio.run(main())
10.3.5. 5. 异步循环#
使用循环来并发执行多个协程:
import asyncio
async def coro(i):
print(f'coro {i} started')
await asyncio.sleep(0.1 * i)
print(f'coro {i} finished')
async def main():
tasks = [coro(i) for i in range(5)]
await asyncio.gather(*tasks)
asyncio.run(main())
10.3.6. 6. 异步上下文管理器#
使用 async with
语句来管理异步上下文:
import asyncio
class AsyncContextManager:
async def __aenter__(self):
print('Entering context')
return self
async def __aexit__(self, exc_type, exc, tb):
print('Exiting context')
async def main():
async with AsyncContextManager() as manager:
print('Inside context')
asyncio.run(main())
10.3.7. 7. 异步锁#
使用 asyncio.Lock()
来处理并发访问共享资源:
import asyncio
async def locked_coro(lock, x):
async with lock:
print(f'Task {x} obtained lock')
await asyncio.sleep(1)
print(f'Task {x} releasing lock')
async def main():
lock = asyncio.Lock()
tasks = [locked_coro(lock, i) for i in range(3)]
await asyncio.gather(*tasks)
asyncio.run(main())
10.3.8. 8. 异步信号处理#
使用 asyncio.get_event_loop()
来处理信号:
import asyncio
import signal
async def main():
loop = asyncio.get_running_loop()
for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame), lambda: print(f'Received {signame}'))
print('Press Ctrl+C to stop')
await asyncio.sleep(3600) # 模拟长时间运行的任务
asyncio.run(main())
这些示例展示了 asyncio
的一些基本和高级用法,帮助你理解如何使用 asyncio
来编写异步代码。