python:协程及任务创建

Python 中的协程(Coroutine)是一种可以暂停和恢复执行的函数,用于实现异步编程。它在 I/O 密集型任务中非常有用,比如网络请求、文件读写等,可以在等待时切换到其他任务,从而提高程序效率。


一、基本概念

  • 协程 vs 线程:协程是用户态的轻量级“线程”,由程序员控制调度(协作式多任务),而线程由操作系统调度(抢占式多任务)。
  • async / await:Python 3.5+ 引入了 async defawait 语法,成为定义和使用协程的标准方式。

二、定义协程

import asyncio

async def calculate(n1:int , n2:int):
    return  n1 + n2


result = calculate(1,2)
print(result)

运行结果如下:

说明你调用了 async def calculate(...),但没有用 awaitasyncio.run() 来真正执行它。Python 只是创建了一个协程对象,然后就结束了程序,导致协程“被遗忘”,于是发出警告。


✅ 正确解决方法

✅ 方法 1:直接用 asyncio.run() 执行协程(适合简单脚本)
import asyncio

async def calculate(n1:int , n2:int):
    result = n1 + n2
    print(result)

asyncio.run(calculate(1,2))

将事件函数calculate扔进协程的run函数程序进行异步执行。

运行结果如下:

💡 注意:asyncio.run() 只能在主线程顶层调用一次,不能嵌套使用。

✅ 方法 2:在 async 函数中 await(推荐用于复杂逻辑)
import asyncio

async def calculate(n1:int , n2:int):
    result = n1 + n2
    print(result)

async def main():
    #在本协程中调用另一个协程,要使用await
    print("main -step 1")
    await calculate(1,2)
    print("main -step 2")

asyncio.run(main())

在 Python 中,await 是用于异步编程的关键字,必须在异步函数(async def 定义的函数) 中使用,其作用是暂停当前异步函数的执行,等待一个可等待对象(Awaitable) 完成,并获取其结果,期间事件循环可以去执行其他任务,实现异步并发。

可等待对象(Awaitable)

await 后面必须跟可等待对象,主要包括以下三类:

  1. 协程对象(Coroutine):由 async def 定义的函数调用后返回的对象。
  2. 任务对象(Task):通过 asyncio.create_task()asyncio.ensure_future() 创建的对象,用于并发执行协程。
  3. 未来对象(Future):表示异步操作的最终结果,是底层的异步原语,通常很少直接使用。

运行结果如下:


三、创建任务

import asyncio
import time


async def call_api(name:str,delay:float):
    print(f"{name} -step 1")
    await asyncio.sleep(delay)
    print(f"{name} -step 2")

async def main():
    #计算消耗的时候
    time_1 = time.perf_counter()

    print("start a coroutine")
    await call_api("A",3)
    print("finished A coroutine")

    print("start b coroutine")
    await call_api("B", 3)
    print("finished B coroutine")

    time_2 = time.perf_counter()
    print(f"Spent{time_2 - time_1}")

asyncio.run(main())

运行结果如下:

相当于整个程序执行完了A,A结束了之后再去执行B,在A“睡眠”的3秒钟时间里面,没有去执行B任务,也就是说A和B两个协程没有并行执行。

那要怎么并行执行呢,这里就要用到asyncio.create_task()

asyncio.create_task() 是 Python 异步编程中用于并发调度协程的重要函数。它将一个协程(coroutine)封装为一个 Task 对象,并立即安排到事件循环中执行,而无需等待你显式 await 它(但通常你仍会 await 以获取结果或处理异常)。

使用asyncio.create_task()的代码如下:

import asyncio
import time


async def call_api(name:str,delay:float):
    print(f"{name} -step 1")
    await asyncio.sleep(delay)
    print(f"{name} -step 2")

async def main():
    #计算消耗的时候
    time_1 = time.perf_counter()

    print("start a coroutine")
    asyncio.create_task(call_api("A",3))
    print("finished A coroutine")

    print("start b coroutine")
    asyncio.create_task(call_api("B", 3))
    print("finished B coroutine")

    time_2 = time.perf_counter()
    print(f"Spent{time_2 - time_1}")

asyncio.run(main())

运行结果:

这段代码的核心问题是:创建了异步任务但没有等待任务完成,导致程序在任务执行前就结束了。

具体表现:

  1. asyncio.create_task() 只是创建任务并加入事件循环,但不会阻塞当前协程
  2. main() 函数在创建完任务后立即结束,事件循环随之关闭,任务根本来不及执行
  3. 最终输出的耗时几乎为 0,且看不到 step 1step 2 的打印(或只能看到部分)

核心原因分析:

根本原因是:main协程执行完毕后,事件循环直接退出,未等待创建的异步任务完成

异步编程中,asyncio.run(main())的作用是:

  1. 创建一个新的事件循环;
  2. 运行main协程直到其完成;
  3. 关闭事件循环(无论是否有其他任务未完成)。

asyncio.create_task(coro)只是将协程包装为任务(Task)并加入事件循环的待执行队列,但不会阻塞当前协程(即main会继续往下执行,不会等任务运行)。

main协程执行到最后一行(print(f"Spent..."))后,main就完成了,此时事件循环被asyncio.run关闭,所有未完成的任务(A 和 B)会被直接终止,因此step2永远不会执行,甚至step1也可能因事件循环关闭过快而无法执行。


四、使用await等待

修正后的代码

我们需要通过 await 等待所有任务完成,代码如下:

import asyncio
import time


async def call_api(name:str,delay:float):
    print(f"{name} -step 1")
    await asyncio.sleep(delay)
    print(f"{name} -step 2")

async def main():
    #计算消耗的时候
    time_1 = time.perf_counter()

    print("start a coroutine")
    task_1 = asyncio.create_task(call_api("A",3))
    print("finished A coroutine")

    print("start b coroutine")
    task_2 = asyncio.create_task(call_api("B", 3))
    print("finished B coroutine")

    # 等待所有任务完成(关键步骤)
    await task_1
    print("task_1 completed")
    await task_2
    print("task_2 completed")

    time_2 = time.perf_counter()
    print(f"Spent{time_2 - time_1}")

asyncio.run(main())

运行结果如下:


我们将任务2中的睡眠时间更改为2秒钟,看下结果如何:

task_2 = asyncio.create_task(call_api("B", 2))

运行结果如下:

将任务1的睡眠时间调整为10秒钟,再看一下效果:

posted @ 2025-12-25 11:34  chenlight  阅读(14)  评论(0)    收藏  举报  来源