Python高级技术之:深入`asyncio`:`Future`、`Task`和`coroutine`的生命周期。

各位观众老爷,大家好!今天咱们来聊聊Python asyncio 里的三位好兄弟:FutureTaskcoroutine,以及它们那剪不断理还乱的生命周期。保证各位听完之后,对异步编程的理解能更上一层楼,以后写代码腰不酸了,腿不疼了,一口气能写十个异步函数!

开胃小菜:asyncio 异步编程的核心概念

在深入这三位主角之前,咱们先简单回顾一下 asyncio 的核心概念,不然一会儿听得云里雾里的,我可不负责。

  • 事件循环 (Event Loop): asyncio 的大脑,负责调度和执行所有的任务。可以把它想象成一个繁忙的交通指挥中心,控制着车辆(任务)的运行。
  • 协程 (Coroutine): asyncio 的基本单元,一种特殊的函数,可以使用 asyncawait 关键字,可以暂停执行,等待其他任务完成,然后再恢复。
  • 任务 (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 的生命周期:

    1. 创建: Future 对象被创建,此时它处于 pending 状态。
    2. 等待: Future 对象等待结果。通常,await 关键字会等待 Future 对象完成。
    3. 完成: Future 对象完成,可以通过 set_result() 设置结果,或者通过 set_exception() 设置异常,或者被取消。
    4. 获取结果: 可以通过 result() 方法获取结果,或者通过 exception() 方法获取异常。

第二位主角:Task – 协程的管家

Taskasyncio 中用于管理协程的对象。它继承自 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 的生命周期:

    1. 创建: Task 对象被创建,此时它处于 pending 状态。
    2. 就绪: Task 对象被添加到事件循环中,等待被调度执行。
    3. 运行: Task 对象开始执行。
    4. 阻塞: Task 对象遇到 await 关键字,暂停执行,等待其他 FutureTask 完成。
    5. 恢复: Task 对象等待的 FutureTask 完成,恢复执行。
    6. 完成: Task 对象执行完毕,返回结果或抛出异常,或者被取消。

第三位主角:coroutine – 异步的发动机

coroutineasyncio 的核心,它是一种特殊的函数,可以使用 asyncawait 关键字。可以将 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 的特点:

    • 可以使用 asyncawait 关键字。
    • 可以暂停执行,等待其他任务完成,然后再恢复。
    • 可以返回一个值,或者抛出一个异常。
    • 调用 coroutine 不会立即执行,而是返回一个 coroutine 对象。需要使用 await 关键字或者 asyncio.create_task() 函数来启动它。
  • coroutine 的生命周期:

    1. 定义: coroutine 函数被定义。
    2. 创建: 调用 coroutine 函数,创建一个 coroutine 对象。
    3. 等待: 使用 await 关键字等待 coroutine 对象完成,或者使用 asyncio.create_task() 函数创建一个 Task 对象来管理 coroutine 的执行。
    4. 执行: coroutine 对象开始执行,遇到 await 关键字会暂停执行,等待其他 FutureTask 完成。
    5. 完成: coroutine 对象执行完毕,返回结果或抛出异常。

三位主角的爱恨情仇:FutureTaskcoroutine 的关系

这三位主角并不是孤立存在的,它们之间有着密切的联系:

  • Task 继承自 Future,所以 Task 拥有 Future 的所有特性。
  • Task 用于管理 coroutine 的执行状态。
  • coroutineasyncio 的核心,它是一种特殊的函数,可以使用 asyncawait 关键字。

可以用一张表格来总结它们的关系:

对象 作用 特点
Future 代表一个尚未完成的计算结果。 占位符,等待结果的到来。可以设置结果、异常或取消。
Task 用于管理 coroutine 的执行状态。 继承自 Future,可以启动、取消和获取 coroutine 的结果。
coroutine asyncio 的核心,一种特殊的函数,可以使用 asyncawait 关键字。 可以暂停执行,等待其他任务完成,然后再恢复。需要使用 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())

在这个例子中:

  1. fetch_url() 是一个 coroutine,它模拟了一个网络请求。
  2. asyncio.create_task() 函数创建了多个 Task 对象,用于管理每个 coroutine 的执行。
  3. asyncio.gather() 函数等待所有 Task 完成,并返回结果。

通过这个例子,我们可以看到 FutureTaskcoroutine 如何协同工作,完成异步任务。

进阶技巧:asyncio.wait()asyncio.as_completed()

除了 asyncio.gather() 函数,asyncio 还提供了 asyncio.wait()asyncio.as_completed() 函数,用于更灵活地管理 Task

  • asyncio.wait(): 等待多个 FutureTask 完成,可以设置超时时间。
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(): 按照完成的顺序返回 FutureTask
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 里的 FutureTaskcoroutine,以及它们的生命周期。希望通过这次讲座,各位能够更好地理解异步编程,写出更加高效、优雅的 Python 代码。记住,异步编程不是银弹,它只适用于特定的场景。在选择使用异步编程之前,一定要仔细评估,看看它是否真的能解决你的问题。

最后,祝各位编程愉快,bug 永远远离!咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注