Python 的asend是异步生成器协议里的一个底层方法很多人刚接触时容易把它和普通的send搞混或者觉得它没什么用。其实在异步编程里asend扮演着一个挺微妙的角色尤其是在处理协程之间的双向通信时。先说说这个东西到底是什么。如果你写过普通生成器应该知道gen.send(value)这个用法你不仅可以从生成器里yield出值还可以往生成器里塞值进去生成器内部用yield表达式的返回值来接收。asend做的事情本质上完全一样只不过它是给异步生成器用的。一个异步生成器函数用async def定义里面有yield的实例会有asend方法调用它会向这个异步生成器发送一个值同时等待它下一次yield出来的值。为了讲得具体点可以想象一个场景你写了一个异步生成器它每隔几秒产生一次传感器读数。但有时候你想从外部动态调整它的采样周期比如根据系统负载临时拉长间隔。普通的做法可能是在外面控制一个全局变量但代码会变得很散。如果用asend你就能直接把调整指令当作一个消息发送进去生成器内部用yield接收然后据此改变行为。这种方式把控制逻辑收拢在同一个流里可读性和维护性都比全局变量好不少。具体到怎么用先看一个最简单的例子asyncdefsimple_async_sender():offset0whileTrue:dataawaitsome_io_operation()# 假设有个异步IOsentyielddataoffsetifsentisnotNone:offsetsent这里的sent就是通过asend传进来的值。外部调用者可以这样用gensimple_async_sender()awaitgen.asend(None)# 第一次必须传None相当于启动resultawaitgen.asend(10)# 发送offset10并拿到下一个yield值第一轮asend(None)是必须的因为异步生成器还没开始执行需要先推进到第一个yield。之后每次调用asend都会把值传给yield表达式然后等待生成器yield出下一个值。如果不小心在第一次调用了asend传了非 None 值Python 会抛TypeError: cant send non-None value to a just-started generator。再说最佳实践。asend最有价值的地方在于实现生产者-消费者模型特别是当生产者和消费者都需要异步等待的时候。拿日志系统举例你可能有一个异步生成器不断从消息队列拉取日志事件然后外部处理器会把日志等级或过滤规则通过asend动态注入。这样生成器和处理器之间就像是在“对话”而不是单向的数据流。另一个常见场景是协程间的协调——比如有一个异步任务负责探测网络状态当网络变差时外部的调度器可以通过asend给它传递一个“降频”的信号生成器内部据此调整轮询间隔。但也要注意一点asend的调用必须是在当前协程的上下文里不能跨事件循环。如果两个协程跑在不同的事件循环里互相发asend会导致不可预期的行为。另外异步生成器最好配合async with或try/finally来保证资源清理否则很容易出现协程泄漏。讲完这些再对比一下相似技术。asend最直接的近亲当然是普通生成器的send。两者语义相同但asend因为要处理await所以它的内部实现涉及到协程的调度和挂起而普通send在同步环境里只是一次函数调用链的上下文切换。如果你把asend和aclose、athrow放在一起看它们共同构成了异步生成器的完整通信协议类似于协程的send、close、throw三者。另一个常被拿来比较的是contextlib.asynccontextmanager的async with语法。虽然两者都是为异步资源管理而设计的但侧重点完全不同。异步上下文管理器主要解决的是资源的获取和释放比如打开一个连接用完关闭而asend解决的是在异步迭代过程中双向通信的问题。一个形象的类比async with像是一个自动开关门的管理员只管让你进去出来asend则像你在屋子里可以通过对讲机和外面的人讲条件。至于asend和协程里的send方法其实协程async def里用await而不用yield的那种也有一个send但那是底层的coroutine.send通常不会直接碰——它主要用于手动驱动协程和asend的场景不太一样。协程的send其实是单向的或者说是用于在协程启动后给它传递值的依赖具体的await表达式但多数情况下你直接用await调用协程就够了不需要手动send。总而言之asend不是一个常用功能但当你需要在异步迭代过程中“对话”时它是目前最干净的解决方案。如果只是单向拉取数据用async for就足够了没必要自己踩asend的坑。只有当生成器和调用者之间需要双向协商的时候再把这把锤子拿出来。