`async def`可以定义一个调用时不会立即执行的函数,要让它真正运行,你需要1)使用 `await` ,对应下面方法1;或者2)把它放到事件循环中,对应下面方法2和3。例如:(例子如果过长可以先跳过,结合后面的解释阅读)

```python
import asyncio
import time

async def test(name, delay):
    print(f"{name} 开始: {time.strftime('%X')}")
    await asyncio.sleep(delay)
    print(f"{name} 结束: {time.strftime('%X')}")
    return name

async def demo_await_only():
    """方法1:直接await - 顺序执行"""
    print("=== 直接await ===")
    r = test("任务1", 2)
    r1 = await test("任务1", 2)  # 等2秒
    r2 = await test("任务2", 1)  # 再等1秒
    # 总共3秒

async def demo_with_tasks():
    """方法2:创建任务 - 并发执行"""
    print("=== 创建任务 ===")
    # 两个任务都放入事件循环,立即开始
    t1 = asyncio.create_task(test("任务1", 2))
    t2 = asyncio.create_task(test("任务2", 1))
    
    # await只是等待它们完成
    r1 = await t1
    r2 = await t2
    # 总共2秒(最长的任务决定总时间)

async def demo_gather():
    """方法3:使用gather - 自动创建任务"""
    print("=== 使用gather ===")
    # gather会自动创建任务并放入事件循环
    results = await asyncio.gather(
        test("任务1", 2),
        test("任务2", 1)
    )
    # 总共2秒

async def main():
    start = time.time()
    await demo_await_only()
    print(f"耗时: {time.time()-start:.1f}秒\n")
    
    start = time.time()
    await demo_with_tasks()
    print(f"耗时: {time.time()-start:.1f}秒\n")
    
    start = time.time()
    await demo_gather()
    print(f"耗时: {time.time()-start:.1f}秒")

asyncio.run(main())
```

上面的代码展现了3个执行异步函数test的方法。方法1中,由于test函数是异步的,所以`r = test("任务1", 2)` 这一行只会创建一个协程(返回r的类型是一个协程对象),里面的任务并不会实际运行,要等到下面的`await test`才会开始运行,所以当不涉及事件循环时await可以理解为异步函数的开关;

方法2和3都是把任务1和2放进事件循环中,这样当其中一个任务到了await点,可以把这个任务挂起等着,同时事件循环继续运行其他任务直到await完成。任务之间没有优先级(方法2、3都是如此,执行任务的顺序和写代码时的顺序无关),实际的执行顺序取决于取决于任务的I/O等待时间、await点的位置等。

这里有一个问题,await为什么一定可以拿出来等着,万一它的异步函数是一个计算密集型操作怎么办?

从事件循环的层面看,方法1相当于每一行都把一个任务放进事件循环,运行完后拿出来,然后在下一行再把一个任务放进去,所以方法1是串行的;而2和3中的任务是并发的,注意并发不等于并行,并发的意思是在只有一个线程的情况下,当其中一个任务遇到await点,进行不占用CPU的操作(如I/O),事件循环就可以切换到另一个任务,直到这个await运行完毕,继续这个任务。所以这里可能形成一个多层嵌套的await,最内层的await可以让出CPU,这个await包在一个async def里,新的async def又被外面的await引用,层层循环,但最内层一定要有一个能让出CPU的操作,这样才能产生并发的效果。所以await的最内层一定要有让出CPU的操作。当然,最内层如果是计算密集型,也不会有await点了,因为计算密集型不需要暂停。

而2和3的区别在于控制的粒度,2可以单独控制、检查、取消一个任务,可以只拿一个任务的结果,可以指定先运行(先await)什么任务,而方法3只能等所有任务完成一起返回;但是方法3也有3的好处,如果不用单独控制代码更简洁,批量处理任务更方便,还有gather函数自带的`timeout` 选项控制超时(统计循环总时间更方便)。

还要注意,因为普通的def函数必须一刻不停地运行到return,而async def可以被暂停,所以**await只能放在async def的函数里**,所以方法1如果用await直接启动某个async def,外面还得套一层async def,这个async def(一般是main)可以用`asyncio.run(main())`直接启动。

另外asyncio的东西也是异步函数async def,所以和方法1同理,`asyncio.gather(...)`和`asyncio.sleep(delay)`前面也要加await启动执行。

 

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐