asyncio note
一、什么是异步编程?
异步编程是一种编程方式,它允许程序在等待某些任务(如网络请求、文件操作)完成时继续执行其他任务,而不是停下来等。这样可以提高程序的效率,特别是在处理 I/O 密集型任务时。
在传统的同步编程中,任务是按顺序执行的。假设有两个任务,任务1需要2秒,任务2需要1秒,那么总共需要3秒才能完成。
import time
def task_1():
print("开始任务 1")
time.sleep(2) # 模拟一个需要2秒的操作
print("任务 1 完成")
def task_2():
print("开始任务 2")
time.sleep(1) # 模拟一个需要1秒的操作
print("任务 2 完成")
task_1()
task_2()
在上面的代码中,task_1
和 task_2
是按顺序执行的,一个任务结束了才开始下一个。
二、异步编程的基本概念
-
协程(Coroutine):协程是 Python 中一种特殊的函数,它可以在执行过程中暂停,并在稍后恢复执行。协程的定义使用
async def
关键字。 -
await
关键字:await
用于暂停协程的执行,等待另一个协程完成。只有在协程中才能使用await
。 -
事件循环(Event Loop):事件循环是一个循环,它不断地运行,调度任务的执行。在异步编程中,事件循环是用来管理和运行多个任务的核心。
三、asyncio
入门示例
1. 定义一个协程
首先,定义一个简单的协程函数,使用 async def
关键字。
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1) # 模拟一个异步操作,比如等待1秒
print("World")
# 在这个例子中,say_hello 是一个协程函数。在执行到 await asyncio.sleep(1) 时,程序会暂停1秒钟。
2. 运行协程
定义了协程之后,需要一个事件循环来运行它。
# 创建一个事件循环并运行协程
loop = asyncio.get_event_loop()
loop.run_until_complete(say_hello())
loop.close()
这里,创建了一个事件循环 loop
,并使用 run_until_complete
方法来运行 say_hello()
协程,直到它完成。
3. 运行方法
可以使用 asyncio.run()
来运行协程(Python 3.7 及以上版本)。
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
# 使用 asyncio.run() 运行协程
asyncio.run(say_hello())
四、并发执行多个协程
一个协程在等待时,事件循环可以去运行其他协程,这样就可以并发地运行多个任务。
import asyncio
async def task_1():
print("Task 1 start")
await asyncio.sleep(2)
print("Task 1 end")
async def task_2():
print("Task 2 start")
await asyncio.sleep(1)
print("Task 2 end")
async def main():
await asyncio.gather(task_1(), task_2())
asyncio.run(main())
在这个例子中,task_1
和 task_2
会并发执行。即使 task_1
需要 2 秒才能完成,task_2
只需要 1 秒,程序的总执行时间是 2 秒,而不是 3 秒,因为两个任务是同时进行的。
五、小结
可以用以下步骤进行练习。
-
创建自己的协程:尝试创建一个简单的协程函数,比如
async def count_to_five()
,它每秒打印一个数字,从 1 数到 5。 -
使用
await
等待:在你的协程中使用await asyncio.sleep(1)
让它每秒打印一个数字。 -
使用
asyncio.run()
:运行你的协程,看看它是如何工作的。 -
并发执行多个协程:尝试创建多个协程,并用
asyncio.gather()
并发运行它们。
六、有效地应用 asyncio
1. 更多协程和任务管理
更好地管理多个协程和任务,特别是在需要的时候取消任务,或者在所有任务完成后继续执行。
示例:取消任务
import asyncio
async def long_running_task():
print("Task started")
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
print("Task was cancelled")
finally:
print("Task ending")
async def main():
task = asyncio.create_task(long_running_task())
await asyncio.sleep(1)
task.cancel() # 取消任务
try:
await task # 等待任务结束,捕获异常
except asyncio.CancelledError:
print("Caught task cancellation")
asyncio.run(main())
2. 使用 asyncio
的同步工具
asyncio
提供了一些类似于线程同步的工具,如 Lock
、Event
、Condition
和 Semaphore
,用于控制异步任务之间的并发。
示例:使用锁
import asyncio
lock = asyncio.Lock()
async def task_1():
async with lock:
print("Task 1 is running")
await asyncio.sleep(2)
print("Task 1 is done")
async def task_2():
async with lock:
print("Task 2 is running")
await asyncio.sleep(1)
print("Task 2 is done")
async def main():
await asyncio.gather(task_1(), task_2())
asyncio.run(main())
在这个示例中,task_1
和 task_2
是互斥的,因为它们都使用了同一个锁 lock
。通过这种方式,可以避免多个协程同时访问共享资源。
3. 队列和生产者-消费者模式
使用 asyncio.Queue
实现生产者-消费者模式,可以在多个协程之间传递数据。
示例:生产者-消费者
import asyncio
async def producer(queue):
for i in range(5):
await asyncio.sleep(1)
item = f'item {i}'
await queue.put(item)
print(f'Produced {item}')
async def consumer(queue):
while True:
item = await queue.get()
if item is None: # 检查结束标志
break
print(f'Consumed {item}')
await asyncio.sleep(2) # 模拟处理时间
async def main():
queue = asyncio.Queue()
producer_task = asyncio.create_task(producer(queue))
consumer_task = asyncio.create_task(consumer(queue))
await producer_task
await queue.put(None) # 向队列发送结束标志
await consumer_task
asyncio.run(main())
这个例子展示了如何使用 asyncio.Queue
在生产者和消费者之间传递数据。
4. 异步文件操作
虽然 Python 的内置文件操作是阻塞的,但可以使用第三方库(如 aiofiles
)进行异步文件操作。在不阻塞事件循环的情况下读写文件。
示例:异步文件读写
首先,安装 aiofiles
:
pip install aiofiles
然后,编写异步文件操作代码:
import asyncio
import aiofiles
async def write_to_file():
async with aiofiles.open('example.txt', mode='w') as file:
await file.write('Hello, asyncio!\n')
async def read_from_file():
async with aiofiles.open('example.txt', mode='r') as file:
content = await file.read()
print(content)
async def main():
await write_to_file()
await read_from_file()
asyncio.run(main())
5. HTTP 请求和异步网络编程
示例:使用 aiohttp
进行异步 HTTP 请求
首先,安装 aiohttp
:
pip install aiohttp
然后,编写异步 HTTP 请求代码:
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'https://www.example.com')
print(html)
asyncio.run(main())
6. 深入理解事件循环和任务调度
使用 asyncio.get_event_loop()
创建和管理事件循环;在不同的线程中运行事件循环,以及使用 asyncio.run_coroutine_threadsafe()
在其他线程中安全地运行协程。
7. 错误处理和调试
使用调试工具(如 asyncio.get_running_loop().set_debug(True)
)来发现和解决问题。