各位观众老爷,大家好!今天咱们来聊聊Python asyncio
里的三位好兄弟:Future
、Task
和 coroutine
,以及它们那剪不断理还乱的生命周期。保证各位听完之后,对异步编程的理解能更上一层楼,以后写代码腰不酸了,腿不疼了,一口气能写十个异步函数!
开胃小菜:asyncio
异步编程的核心概念
在深入这三位主角之前,咱们先简单回顾一下 asyncio
的核心概念,不然一会儿听得云里雾里的,我可不负责。
- 事件循环 (Event Loop):
asyncio
的大脑,负责调度和执行所有的任务。可以把它想象成一个繁忙的交通指挥中心,控制着车辆(任务)的运行。 - 协程 (Coroutine):
asyncio
的基本单元,一种特殊的函数,可以使用async
和await
关键字,可以暂停执行,等待其他任务完成,然后再恢复。 - 任务 (Task): 协程的包装器,用于管理协程的执行状态。可以将 Task 想象成一个任务管理器,负责启动、取消和获取协程的结果。
- Future: 代表一个尚未完成的计算结果。它有点像一个“承诺”,承诺将来会有一个值,但现在还没有。
第一位主角:Future
– 未来的承诺
Future
对象代表一个异步操作的最终结果。它本身并不包含结果,而只是一个占位符,等待结果的到来。可以把它想象成一个“订单”,下单的时候东西还没送到,但最终你会收到你的快递。
- 创建
Future
对象: 通常情况下,我们不会手动创建Future
对象,asyncio
会自动创建它们。但是,为了理解它的工作方式,咱们还是来手动创建一个:
import asyncio
async def main():
loop = asyncio.get_event_loop()
future = loop.create_future() # 创建一个 Future 对象
print(f"Future done: {future.done()}") # 检查 Future 是否已完成
print(f"Future cancelled: {future.cancelled()}") # 检查 Future 是否已取消
# 设置 Future 的结果
future.set_result("任务完成啦!")
print(f"Future done: {future.done()}")
print(f"Future result: {future.result()}") # 获取 Future 的结果
asyncio.run(main())
输出:
Future done: False
Future cancelled: False
Future done: True
Future result: 任务完成啦!
-
Future
的常用方法:done()
: 返回True
如果Future
已经完成(无论是否成功),否则返回False
。result()
: 返回Future
的结果。如果Future
尚未完成,会阻塞直到结果可用。如果Future
被取消,会抛出CancelledError
异常。如果Future
抛出了异常,会抛出相同的异常。exception()
: 返回Future
的异常。如果Future
已经完成且没有抛出异常,返回None
。set_result(value)
: 设置Future
的结果。只能设置一次。set_exception(exception)
: 设置Future
的异常。只能设置一次。cancel()
: 取消Future
。cancelled()
: 返回True
如果Future
已经被取消,否则返回False
。add_done_callback(callback)
: 添加一个回调函数,当Future
完成时会被调用。
-
Future
的生命周期:- 创建:
Future
对象被创建,此时它处于 pending 状态。 - 等待:
Future
对象等待结果。通常,await
关键字会等待Future
对象完成。 - 完成:
Future
对象完成,可以通过set_result()
设置结果,或者通过set_exception()
设置异常,或者被取消。 - 获取结果: 可以通过
result()
方法获取结果,或者通过exception()
方法获取异常。
- 创建:
第二位主角:Task
– 协程的管家
Task
是 asyncio
中用于管理协程的对象。它继承自 Future
,所以拥有 Future
的所有特性。可以将 Task
想象成一个“项目经理”,负责启动、监控和管理协程的执行。
- 创建
Task
对象: 可以使用asyncio.create_task()
函数或者loop.create_task()
方法创建Task
对象。
import asyncio
async def my_coroutine(delay):
await asyncio.sleep(delay)
return f"协程执行完毕,延迟了 {delay} 秒"
async def main():
task = asyncio.create_task(my_coroutine(2)) # 创建一个 Task 对象
print(f"Task done: {task.done()}")
await asyncio.sleep(1) # 等待 1 秒
print(f"Task done: {task.done()}")
result = await task # 等待 Task 完成并获取结果
print(f"Task result: {result}")
asyncio.run(main())
输出:
Task done: False
Task done: False
Task result: 协程执行完毕,延迟了 2 秒
-
Task
的常用方法:Task
继承了Future
的所有方法,并且还提供了一些额外的方法:cancel()
: 取消Task
。cancelled()
: 返回True
如果Task
已经被取消,否则返回False
。done()
: 返回True
如果Task
已经完成(无论是否成功),否则返回False
。result()
: 返回Task
的结果。如果Task
尚未完成,会阻塞直到结果可用。如果Task
被取消,会抛出CancelledError
异常。如果Task
抛出了异常,会抛出相同的异常。exception()
: 返回Task
的异常。如果Task
已经完成且没有抛出异常,返回None
。add_done_callback(callback)
: 添加一个回调函数,当Task
完成时会被调用。get_coro()
: 返回 Task 包装的协程对象。get_stack(*, limit=None)
: 返回 Task 的堆栈帧列表。print_stack(*, limit=None, file=None)
: 将 Task 的堆栈信息打印到指定文件。
-
Task
的生命周期:- 创建:
Task
对象被创建,此时它处于 pending 状态。 - 就绪:
Task
对象被添加到事件循环中,等待被调度执行。 - 运行:
Task
对象开始执行。 - 阻塞:
Task
对象遇到await
关键字,暂停执行,等待其他Future
或Task
完成。 - 恢复:
Task
对象等待的Future
或Task
完成,恢复执行。 - 完成:
Task
对象执行完毕,返回结果或抛出异常,或者被取消。
- 创建:
第三位主角:coroutine
– 异步的发动机
coroutine
是 asyncio
的核心,它是一种特殊的函数,可以使用 async
和 await
关键字。可以将 coroutine
想象成一个“发动机”,驱动着异步程序的运行。
- 定义
coroutine
: 使用async
关键字定义一个coroutine
。
import asyncio
async def my_coroutine(name):
print(f"协程 {name} 开始执行")
await asyncio.sleep(1) # 模拟耗时操作
print(f"协程 {name} 执行完毕")
return f"协程 {name} 的结果"
async def main():
result = await my_coroutine("Task1") # 调用协程
print(result)
asyncio.run(main())
输出:
协程 Task1 开始执行
协程 Task1 执行完毕
协程 Task1 的结果
-
coroutine
的特点:- 可以使用
async
和await
关键字。 - 可以暂停执行,等待其他任务完成,然后再恢复。
- 可以返回一个值,或者抛出一个异常。
- 调用
coroutine
不会立即执行,而是返回一个coroutine
对象。需要使用await
关键字或者asyncio.create_task()
函数来启动它。
- 可以使用
-
coroutine
的生命周期:- 定义:
coroutine
函数被定义。 - 创建: 调用
coroutine
函数,创建一个coroutine
对象。 - 等待: 使用
await
关键字等待coroutine
对象完成,或者使用asyncio.create_task()
函数创建一个Task
对象来管理coroutine
的执行。 - 执行:
coroutine
对象开始执行,遇到await
关键字会暂停执行,等待其他Future
或Task
完成。 - 完成:
coroutine
对象执行完毕,返回结果或抛出异常。
- 定义:
三位主角的爱恨情仇:Future
、Task
和 coroutine
的关系
这三位主角并不是孤立存在的,它们之间有着密切的联系:
Task
继承自Future
,所以Task
拥有Future
的所有特性。Task
用于管理coroutine
的执行状态。coroutine
是asyncio
的核心,它是一种特殊的函数,可以使用async
和await
关键字。
可以用一张表格来总结它们的关系:
对象 | 作用 | 特点 |
---|---|---|
Future |
代表一个尚未完成的计算结果。 | 占位符,等待结果的到来。可以设置结果、异常或取消。 |
Task |
用于管理 coroutine 的执行状态。 |
继承自 Future ,可以启动、取消和获取 coroutine 的结果。 |
coroutine |
asyncio 的核心,一种特殊的函数,可以使用 async 和 await 关键字。 |
可以暂停执行,等待其他任务完成,然后再恢复。需要使用 await 关键字或者 asyncio.create_task() 函数来启动它。 |
举个栗子:模拟一个网络请求
为了更好地理解这三位主角的生命周期,咱们来模拟一个网络请求的场景:
import asyncio
import aiohttp
async def fetch_url(url):
"""
模拟网络请求
"""
print(f"开始请求 {url}")
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
await asyncio.sleep(1) # 模拟网络延迟
print(f"请求 {url} 完成,状态码:{response.status}")
return await response.text()
async def main():
urls = [
"https://www.example.com",
"https://www.baidu.com",
"https://www.python.org"
]
tasks = [asyncio.create_task(fetch_url(url)) for url in urls] # 创建 Task 列表
results = await asyncio.gather(*tasks) # 等待所有 Task 完成
for i, result in enumerate(results):
print(f"网址 {urls[i]} 的内容:{result[:50]}...") # 打印每个网址的前 50 个字符
asyncio.run(main())
在这个例子中:
fetch_url()
是一个coroutine
,它模拟了一个网络请求。asyncio.create_task()
函数创建了多个Task
对象,用于管理每个coroutine
的执行。asyncio.gather()
函数等待所有Task
完成,并返回结果。
通过这个例子,我们可以看到 Future
、Task
和 coroutine
如何协同工作,完成异步任务。
进阶技巧:asyncio.wait()
和 asyncio.as_completed()
除了 asyncio.gather()
函数,asyncio
还提供了 asyncio.wait()
和 asyncio.as_completed()
函数,用于更灵活地管理 Task
。
asyncio.wait()
: 等待多个Future
或Task
完成,可以设置超时时间。
import asyncio
async def my_coroutine(delay):
await asyncio.sleep(delay)
return f"协程执行完毕,延迟了 {delay} 秒"
async def main():
task1 = asyncio.create_task(my_coroutine(2))
task2 = asyncio.create_task(my_coroutine(1))
done, pending = await asyncio.wait([task1, task2], timeout=1.5) # 设置超时时间为 1.5 秒
print(f"已完成的任务:{done}")
print(f"未完成的任务:{pending}")
for task in done:
print(f"任务结果:{await task}")
for task in pending:
task.cancel() # 取消未完成的任务
asyncio.run(main())
输出:
已完成的任务:{<Task pending name='Task-2' coro=<my_coroutine() running at ...> finished result='协程执行完毕,延迟了 1 秒'>}
未完成的任务:{<Task pending name='Task-1' coro=<my_coroutine() running at ...> cancelled>}
任务结果:协程执行完毕,延迟了 1 秒
asyncio.as_completed()
: 按照完成的顺序返回Future
或Task
。
import asyncio
async def my_coroutine(delay):
await asyncio.sleep(delay)
return f"协程执行完毕,延迟了 {delay} 秒"
async def main():
task1 = asyncio.create_task(my_coroutine(2))
task2 = asyncio.create_task(my_coroutine(1))
for task in asyncio.as_completed([task1, task2]):
result = await task
print(f"任务结果:{result}")
asyncio.run(main())
输出:
任务结果:协程执行完毕,延迟了 1 秒
任务结果:协程执行完毕,延迟了 2 秒
总结
今天咱们深入探讨了 asyncio
里的 Future
、Task
和 coroutine
,以及它们的生命周期。希望通过这次讲座,各位能够更好地理解异步编程,写出更加高效、优雅的 Python 代码。记住,异步编程不是银弹,它只适用于特定的场景。在选择使用异步编程之前,一定要仔细评估,看看它是否真的能解决你的问题。
最后,祝各位编程愉快,bug 永远远离!咱们下期再见!