别怕异步:`async` 和 `await` 的简单理解

有时候你会看到 Python 代码里突然出现 async defawait,感觉像在说外语。你是不是心里一紧:"完了,这回真跟不上了。" 别慌,我第一次看到也懵------其实它没那么复杂,只是 Python 在说:"这个函数可能会等一会儿,但它不会傻站着等,而是可以先去干点别的。"

我们用一个你肯定经历过的场景来比喻:点咖啡 。你走进咖啡馆,点了一杯拿铁。如果店员做好才让你付钱,你必须在柜台前傻站着等三分钟,什么也干不了------这就是 同步 。但如果店员给你一个叫号器,说"好了会震动",你就可以先去旁边刷手机、回消息,等叫号器震了再去取------这就是 异步 。Python 的 asyncawait 就是帮你实现"拿着叫号器边等边干别的事"的工具。

同步与异步的区别:一个办完再办下一个,还是同时排多个队

同步 :一件事做完,再做下一件事。就像你排队买票,必须等第一个人买完,第二个人才能上前。如果第一个人掏钱掏了半天,后面所有人都得干等。Python 普通的函数(没有 async)就是同步的------它调用一个耗时操作(比如等 AI 回复),整个程序就会卡在那里,直到得到结果才能往下走。

异步:你可以在等待某个慢操作的同时,去处理别的任务。就像你同时有好几个叫号器:咖啡好了、奶茶好了、汉堡好了,哪个先震动你就先去取哪个。Python 的异步代码允许你"同时"发起多个耗时操作,然后等它们各自完成时分别处理,而不是一个接一个地傻等。

python 复制代码
# 同步版本(普通函数)------ 模拟耗时操作
import time

def slow_sync():
    time.sleep(2)   # 模拟等待2秒
    print("完成")

print("开始")
slow_sync()          # 程序会卡在这里2秒,什么也不能做
print("结束")        # 2秒后才执行这一行
python 复制代码
# 异步版本(需要特殊写法)------ 先别管细节,感受气质
import asyncio

async def slow_async():
    await asyncio.sleep(2)   # 等待2秒,但不阻塞其他任务
    print("完成")

# 这段代码不能直接运行,只是让你对比

同步是"做完一件再下一件",异步是"同时等很多件,哪件先好处理哪件"。

在 LangGraph 里哪里会遇到它?

LangGraph 在处理多个节点(比如多个工具调用、多个模型并发请求)时,为了不浪费等待时间,内部大量使用异步。如果你要同时让 AI 分析三篇不同的文章,同步版本需要等第一篇回来再发第二篇,异步版本可以三篇一起发出去,谁先回来就先处理谁。理解了这个区别,你就知道为什么 LangGraph 的示例里经常出现 async def

async def 定义一个"可以暂停等待"的函数

普通的函数用 def 定义,一调用就会从头跑到尾,中间不能停。而用 async def 定义的函数,叫做 异步函数。它可以在执行到某个"等一会儿"的地方(比如等网络返回)主动暂停,让 Python 去执行其他异步任务,等那个耗时的操作完成了再回来继续。

你可以把 async def 理解成 "带暂停按钮的函数"。它像一个可以中途去泡杯茶、回来继续干的员工,而不是必须钉在座位上一动不动。

python 复制代码
# 定义一个异步函数
async def fetch_data():
    print("开始获取数据...")
    # 这里本该是 await 某个耗时操作
    print("数据获取完成")

注意:如果你只是定义 async def 但里面没有 await,它其实和普通函数差不多。但它的真正威力在于配合 await 使用。

另外非常重要的一点:异步函数不能直接像普通函数那样调用 。你写 fetch_data() 不会真的执行它,而是得到一个"协程对象"(可以理解成"一个等待启动的任务")。你需要用特定的方式(比如 asyncio.run())来启动它。

在 LangChain/LangGraph 里哪里会遇到它?

当你看到 LangGraph 示例里写 async def my_node(state):,那就是在定义一个异步的图节点。这个节点在处理过程中可能需要调用多个模型或者等待外部 API,用 async def 可以让它在该等的时候"让出"控制权,让其他节点同时运行。

await 用来等待一个耗时的操作,但不阻塞其他任务

await 是"我就在这儿等,但我不死等"的关键字。它后面跟着一个"可等待"的东西(通常是另一个异步函数或操作)。当 Python 遇到 await 时,它会说:"好了,这个操作需要花时间,我先让 CPU 去执行别的异步任务,等这个操作完成了再回来继续往下走。"

回到点咖啡的例子:你拿着叫号器,await 就是"我把叫号器捏在手里,然后去刷手机"。咖啡好了,叫号器震动,你才回去取。在 await 期间,你没有阻塞------你同时还在刷手机(相当于 Python 在执行别的任务)。

python 复制代码
import asyncio

async def make_coffee():
    print("开始做咖啡...")
    await asyncio.sleep(3)   # 模拟耗时3秒的咖啡制作
    print("咖啡做好了")
    return "拿铁"

async def main():
    print("点了一杯咖啡")
    result = await make_coffee()   # 等待咖啡做好,但期间可以做别的事
    print(f"拿到{result}")

# 运行
asyncio.run(main())

输出:

复制代码
点了一杯咖啡
开始做咖啡...
(等待3秒,但这里没有其他任务,所以实际还是等了3秒)
咖啡做好了
拿到拿铁

注意:上面的例子只有一个任务,所以 await 看起来就像普通等待一样。但如果你有两个异步任务,就可以"同时"等待它们:

python 复制代码
async def main():
    task1 = asyncio.create_task(make_coffee())   # 开始做咖啡,不等待
    task2 = asyncio.create_task(make_tea())      # 开始泡茶,也不等待
    coffee = await task1   # 等咖啡完成
    tea = await task2      # 等茶完成(可能已经好了)

这样总耗时大约等于最慢的那个任务,而不是两者之和。

在 LangGraph 里哪里会遇到它?

你会经常看到类似这种模式:response = await model.ainvoke(input)。这里的 ainvoke 是模型的异步调用方法(a 代表 async)。await model.ainvoke(...) 会发起请求,然后在等待网络返回的同时,LangGraph 可以去执行其他可并行的节点。如果你不用 await,而是用普通的 model.invoke,整个程序就会卡住直到模型返回,浪费了等待时间。

简单认识 asyncio.run(),怎么把异步函数跑起来

前面说了,异步函数不能直接调用。你需要一个"启动器"来运行异步代码。Python 标准库提供了 asyncio 这个工具箱,其中的 asyncio.run() 就是最常用的启动器。

asyncio.run() 接收一个异步函数(通常叫 main()),它会创建一套异步运行环境,执行这个异步函数,然后清理环境。你只需要记住:所有异步代码的入口都要用 asyncio.run() 启动

python 复制代码
import asyncio

async def say_hello():
    await asyncio.sleep(1)
    print("Hello async!")

# 启动异步函数
asyncio.run(say_hello())

这就是最简模式。如果你有一个 async def main(),就写 asyncio.run(main())

注意:asyncio.run() 只能在程序的主线程调用一次。在 Jupyter Notebook 等特殊环境里可能需要别的方法,但普通 .py 脚本中就这么用。

在 LangChain/LangGraph 里哪里会遇到它?

当你写一个完全异步的 LangGraph 应用时,你的主程序最后会有一行 asyncio.run(main())。在官方示例中,你有时会看到 if __name__ == "__main__": asyncio.run(main()) 这种结构。你不需要自己设计复杂的异步逻辑,但至少要能认出:这就是"把异步函数跑起来"的开关。

另外,很多 LangChain 组件同时提供了同步方法(invoke)和异步方法(ainvoke)。如果你想在自己的代码里调用异步方法,就要用 await,并且整个调用链都要是异步函数,最终用 asyncio.run() 启动。但如果你觉得麻烦,完全可以只用同步版本(invoke)------LangChain 会帮你处理好内部转换。不过,识别异步代码 的能力还是很重要的,因为在 LangGraph 的复杂示例里你会频繁见到它。

本篇小结与下集预告

小结 :今天我们认识了异步的世界。同步是一个接一个做事情,异步是同时等很多事情,哪个先好了就处理哪个。async def 定义了一个可以暂停等待的函数,await 用来等待耗时操作但不阻塞其他任务,而 asyncio.run() 是整个异步程序的启动按钮。现在你再看到 LangGraph 示例里的 async def my_node(state):await some_async_operation(),应该能翻译出来:"这个节点可以在等待时让位给别的节点,等结果回来再继续,这样总速度更快。"

下集预告 :下一篇文章,我们会聊一聊"整理你的房间"------也就是模块、包和文件组织。当你开始动手写一个稍微大一点的 LangChain 小项目时,把所有代码都塞进一个 .py 文件会非常混乱。我们会教你如何把不同功能的代码拆分到不同的文件,然后用 import 把它们组织起来。你还会明白 if __name__ == "__main__": 这行"咒语"到底是干什么的。放心,整理房间这件事你一定擅长。我们明天见~

相关推荐
__log1 小时前
ComfyUI 集成技术方案分析报告
javascript·python·django
njsgcs1 小时前
c# solidworks 标注攻牙
开发语言·c#·solidworks
吴声子夜歌2 小时前
Java——显示条件
java·开发语言
有味道的男人2 小时前
1688 商品价格 API:阶梯价、代发价、批发价实时查询
开发语言·windows·python
范范@2 小时前
python基础-for循环和列表
开发语言·python
小白学大数据2 小时前
Python 爬虫动态 JS 渲染与无头浏览器实战选型指南
开发语言·javascript·爬虫·python
朔北之忘 Clancy2 小时前
2026 年 3 月青少年软编等考 C 语言一级真题解析
c语言·开发语言·c++·学习·青少年编程·题解·一级
m0_609160492 小时前
如何用 some 检测数组中是否存在至少一个满足条件的项
jvm·数据库·python