深入理解 Python 的 asyncio 库
Python 的 asyncio
库是一个强大的异步 I/O 框架,用于处理并发和异步编程。它提供了一种基于协程的方式来处理异步任务,使得编写异步代码更加简单和直观。
1. 什么是 asyncio?
asyncio
是 Python 3.4 引入的标准库,用于编写协程和异步代码。它基于事件循环(Event Loop)的概念,通过异步任务(coroutines)和 Future 对象来实现非阻塞的并发操作。异步编程的目标是提高代码执行效率,特别是在需要等待 I/O 操作(如网络请求、文件读写等)时,能够释放 CPU 资源,从而提高程序的并发性能。
2. 协程和任务
在 asyncio
中,协程是异步编程的基本单元。通过 async def
关键字定义的函数可以被视为协程。协程是可以暂停和恢复执行的函数,使得程序能够在等待 I/O 操作的同时执行其他任务。
任务(Task)是协程的一种封装,它可以在事件循环中并发执行。使用 asyncio.create_task()
函数可以创建任务,然后将任务添加到事件循环中。
3. 事件循环
事件循环是 asyncio
的核心。它负责调度和执行协程,处理事件和 I/O 操作。通过 asyncio.get_event_loop()
获取默认事件循环,然后使用 loop.run_until_complete()
来运行任务直到完成。
- 创建事件循环
通过 asyncio.get_event_loop()
获取当前线程的事件循环,或者使用 asyncio.new_event_loop()
创建一个新的事件循环。一般情况下,我们使用 asyncio.run()
来运行主协程,该函数会自动创建和管理事件循环。
import asyncio
async def main():
# 主协程逻辑
# 运行主协程
asyncio.run(main())
- 启动事件循环
使用 loop.run_until_complete(coroutine)
启动事件循环并运行指定的协程,直到协程完成。
import asyncio
async def main():
# 协程逻辑
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
- 关闭事件循环
使用 loop.close()
关闭事件循环。在大多数情况下,不需要手动关闭事件循环,因为 asyncio.run()
在协程完成后会自动关闭事件循环。
import asyncio
async def main():
# 协程逻辑
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
- 运行多个任务
使用 asyncio.create_task(coroutine)
创建任务,然后使用 await asyncio.gather(task1, task2, ...)
等待多个任务同时完成。
import asyncio
async def task1():
# 任务1逻辑
async def task2():
# 任务2逻辑
async def main():
t1 = asyncio.create_task(task1())
t2 = asyncio.create_task(task2())
await asyncio.gather(t1, t2)
- 异步调度
事件循环通过调度器(Scheduler)来实现异步调度。协程中的 await
表达式用于暂停协程的执行,将控制权还给事件循环。当等待的条件满足时,事件循环会重新调度协程的执行。
import asyncio
async def my_coroutine():
print("Start Coroutine")
await asyncio.sleep(2) # 异步等待
print("End Coroutine")
asyncio.run(my_coroutine())
- 定时器
使用 asyncio.sleep(seconds)
来创建一个定时器,让协程在指定时间后恢复执行。
import asyncio
async def my_coroutine():
print("Start Coroutine")
await asyncio.sleep(2) # 等待2秒
print("End Coroutine")
asyncio.run(my_coroutine())
4. 异步 I/O 操作
asyncio
通过使用 async/await
语法来处理异步 I/O 操作。例如,可以使用 asyncio.sleep()
来模拟一个异步的等待操作。
import asyncio
async def example_coroutine():
print("Start")
await asyncio.sleep(2)
print("End")
asyncio.run(example_coroutine())
在上面的例子中,asyncio.sleep(2)
模拟了一个等待 2 秒的异步操作,而程序在等待期间不会被阻塞。
使用asyncio来发送http请求:
import asyncio
import time
import requests
url = 'https://www.baidu.com'
async def fetch_url(url):
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, requests.get, url)
return response.text
async def main():
await asyncio.gather(*(fetch_url(url) for _ in range(10)))
# 运行主协程
print(time.time())
asyncio.run(main())
print(time.time())
print("------------------")
# 相比异步的方式,同步发送10次请求需要更长的时间
print(time.time())
for _ in range(10):
requests.get(url)
print(time.time())
5. wait & gather
asyncio.wait()
和 asyncio.gather()
都是 asyncio
中用于等待多个协程完成的方法,但它们之间有一些重要的区别。
asyncio.wait()
-
等待条件:
asyncio.wait()
用于等待多个协程中的任意一个或全部完成。可以通过设置return_when
参数来指定等待的条件,可选值包括:asyncio.FIRST_COMPLETED
:等待任意一个协程完成。asyncio.FIRST_EXCEPTION
:等待第一个协程抛出异常。asyncio.ALL_COMPLETED
:等待所有协程完成。
-
返回值:
asyncio.wait()
返回一个元组,包含两个集合:- 第一个集合包含已完成的任务(Task)。
- 第二个集合包含未完成的任务。
-
用法示例:
done, pending = await asyncio.wait([coro1(), coro2(), coro3()], return_when=asyncio.ALL_COMPLETED)
asyncio.gather()
-
等待条件:
asyncio.gather()
用于同时运行多个协程,并等待它们全部完成。
-
返回值:
asyncio.gather()
返回一个包含所有协程结果的列表。
-
用法示例:
results = await asyncio.gather(coro1(), coro2(), coro3())
总结区别:
-
返回值类型:
asyncio.wait()
返回一个包含已完成和未完成任务的元组。asyncio.gather()
返回一个包含所有协程结果的列表。
-
等待条件:
asyncio.wait()
允许你设置等待条件,可以等待任意一个或全部协程完成,或者等待第一个异常。asyncio.gather()
等待所有协程同时完成。
-
适用场景:
- 如果你需要对每个协程的完成状态进行额外的处理,或者需要根据条件选择部分协程执行,可以使用
asyncio.wait()
。 - 如果你希望并发运行多个协程,并在所有协程都完成后获取它们的结果,可以使用
asyncio.gather()
。
- 如果你需要对每个协程的完成状态进行额外的处理,或者需要根据条件选择部分协程执行,可以使用
下面是一个简单的示例,演示了两者的用法:
import asyncio
async def coro1():
await asyncio.sleep(1)
print("Task 1 completed")
return 1
async def coro2():
await asyncio.sleep(2)
print("Task 2 completed")
return 2
async def coro3():
await asyncio.sleep(0.5)
print("Task 3 completed")
return 3
async def task1():
pass
async def main():
# 使用 asyncio.wait()
task1 = asyncio.create_task(coro1())
task2 = asyncio.create_task(coro2())
task3 = asyncio.create_task(coro3())
done, pending = await asyncio.wait([task1, task2, task3], return_when=asyncio.ALL_COMPLETED)
print(f"Done tasks: {[task.result() for task in done]}")
print(f"Pending tasks: {[task for task in pending]}")
# 使用 asyncio.gather()
results = await asyncio.gather(coro1(), coro2(), coro3())
print(f"Gathered results: {results}")
# 运行主协程
asyncio.run(main())
这个例子中,asyncio.wait()
返回一个已完成的任务集合和一个未完成的任务集合,而 asyncio.gather()
直接返回所有协程的结果列表。
6. Task & Future
在 asyncio
中,Task
和 Future
都是用于处理异步操作的类。它们之间有一些区别,但也有相似之处。
Future
asyncio.Future
是一个表示异步操作结果的对象。它是一个容器,用于存储异步操作的最终结果或异常。Future
对象通常由 asyncio
方法返回,表示一个尚未完成的异步操作。
用法
-
手动创建 Future 对象:
import asyncio async def my_coroutine(): fut = asyncio.Future() fut.set_result("Hello, Future!") return fut async def main(): result = await my_coroutine() print(result.result()) asyncio.run(main())
-
通过
asyncio.ensure_future()
包装协程:import asyncio async def my_coroutine(): return "Hello, Future!" async def main(): fut = asyncio.ensure_future(my_coroutine()) print(await fut) asyncio.run(main())
Task
asyncio.Task
是对 Future
的高级封装,它表示一个异步操作的执行单元。通常情况下,我们不直接创建 Task
对象,而是通过 asyncio.create_task()
或 asyncio.ensure_future()
来创建。asyncio.Task
是 asyncio.Future
的子类,它表示一个异步操作的执行单元,通常对应一个协程。Task
提供了一些额外的方法和属性,用于管理和控制任务的执行。
创建Task
-
使用
asyncio.create_task()
创建任务:import asyncio async def my_coroutine(): return "Hello, Task!" async def main(): task = asyncio.create_task(my_coroutine()) print(await task) asyncio.run(main())
-
使用
asyncio.ensure_future()
包装协程创建任务:import asyncio async def my_coroutine(): return "Hello, Task!" async def main(): task = asyncio.ensure_future(my_coroutine()) print(await task) asyncio.run(main())
Task常见方法
asyncio.Task.get_loop()
-
描述:返回与任务关联的事件循环
-
用法示例:
import asyncio async def my_coroutine(): print("Task loop:", asyncio.Task.get_loop()) async def main(): task = asyncio.create_task(my_coroutine()) await task asyncio.run(main())
asyncio.Task.cancel()
-
描述:取消任务,将任务的状态设置为已取消
-
用法示例:
import asyncio async def my_coroutine(): try: await asyncio.sleep(5) except asyncio.CancelledError: print("Task was cancelled") async def main(): task = asyncio.create_task(my_coroutine()) await asyncio.sleep(2) task.cancel() await asyncio.sleep(1) # 等待任务响应取消 asyncio.run(main())
asyncio.Task.done()
-
描述:返回一个
True
或False
,表示任务是否已完成 -
用法示例:
import asyncio async def my_coroutine(): print("Task is done:", asyncio.current_task().done()) async def main(): task = asyncio.create_task(my_coroutine()) await task asyncio.run(main())
asyncio.Task.exception()
-
描述:返回任务的异常,如果任务没有异常则返回
None
-
用法示例:
import asyncio async def my_coroutine(): raise ValueError("Custom error") async def main(): task = asyncio.create_task(my_coroutine()) try: await task except Exception as e: print("Task exception:", task.exception()) asyncio.run(main())
asyncio.Task.result()
-
描述:返回任务的结果。如果任务还没有完成,则引发
asyncio.InvalidStateError
异常 -
用法示例:
import asyncio async def my_coroutine(): return "Task result" async def main(): task = asyncio.create_task(my_coroutine()) await task print("Task result:", task.result()) asyncio.run(main())
asyncio.Task.add_done_callback(callback, *args)
-
描述:添加一个回调函数,在任务完成时调用
-
用法示例:
import asyncio def callback(task, *args): print("Callback executed") async def my_coroutine(): print("Task completed") async def main(): task = asyncio.create_task(my_coroutine()) task.add_done_callback(callback) await task asyncio.run(main())
Future 和 Task 区别:
-
创建方式:
Future
对象可以手动创建,也可以由asyncio.ensure_future()
或asyncio.Future()
创建。Task
对象通常由asyncio.create_task()
或asyncio.ensure_future()
创建。
-
使用场景:
Future
主要用于表示异步操作的结果或异常,可以手动设置结果。Task
是对Future
的高级封装,用于表示协程的执行单元,通常在创建时就已经与某个协程绑定。
-
状态检查:
- 通过
fut.done()
可以检查Future
是否已经完成。 Task
对象有额外的状态检查方法,如task.cancelled()
和task.running()
。
- 通过
在实际应用中,Task
对象更常用,因为它是对 Future
的进一步封装,提供了更多的功能,例如取消任务、绑定回调函数等。但在某些情况下,可能仍然需要直接使用 Future
对象,例如手动控制异步操作的完成状态。
asyncio库总结
asyncio
库提供了多个方法和类,用于编写异步代码。以下是一些常用的 asyncio
方法和类的概览:
主要方法和类:
-
asyncio.run(coroutine, *, debug=False)
:- 用于运行一个协程,启动事件循环,然后在协程执行完成后关闭事件循环。
-
asyncio.create_task(coroutine, *, name=None)
:- 用于创建一个任务,将协程添加到事件循环中,并返回任务对象。
-
asyncio.gather(*coros, loop=None, return_exceptions=False)
:- 用于同时运行多个协程,并等待它们全部完成。返回一个包含所有协程结果的列表。
-
asyncio.wait(coros, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
:- 用于等待多个协程中的一个或全部完成。返回一个包含已完成任务的集合。
-
asyncio.sleep(delay, result=None, *, loop=None)
:- 用于在协程中进行异步休眠,推迟执行。
事件循环控制方法:
-
loop.run_until_complete(future)
:- 用于运行事件循环直到指定的 Future 完成。通常用于运行主协程。
-
loop.run_forever()
:- 用于启动事件循环并持续运行,直到调用
loop.stop()
。
- 用于启动事件循环并持续运行,直到调用
-
loop.stop()
:- 用于停止事件循环。
-
loop.close()
:- 用于关闭事件循环。
其他工具方法:
-
asyncio.shield(what, *, loop=None)
:- 用于创建一个被保护的 Future 对象,防止被取消。
-
asyncio.ensure_future(coro_or_future, *, loop=None)
:- 用于将协程或 Future 对象包装成 Future 对象。
-
asyncio.current_task(loop=None)
:- 用于获取当前任务(Task)对象。
-
asyncio.all_tasks(loop=None)
:- 用于获取事件循环中的所有任务。
-
asyncio.run_coroutine_threadsafe(coro, loop)
:- 用于在不同线程中运行协程。
信号处理:
loop.add_signal_handler(signal, callback, *args)
:- 用于添加信号处理器。
定时器和调度:
-
loop.call_later(delay, callback, *args, context=None)
:- 用于在指定延迟后调用指定回调函数。
-
loop.call_at(when, callback, *args, context=None)
:- 用于在指定时间后调用指定回调函数。
异步 I/O 操作:
-
loop.add_reader(fd, callback, *args)
和loop.remove_reader(fd)
:- 用于监听文件描述符上的可读事件。
-
loop.add_writer(fd, callback, *args)
和loop.remove_writer(fd)
:- 用于监听文件描述符上的可写事件。