回调函数转async def的核心是用asyncio.Future搭桥:创建Future,在回调中调用set_result/set_exception,再await它;需防跨线程、重复设置、未await泄漏、同步触发异常等问题。

回调函数怎么转成 async def?
核心思路是用 asyncio.Future 搭桥:创建一个未完成的 Future,把回调逻辑塞进去,让它在回调触发时设置结果或异常,然后用 await 等它完成。
常见错误是直接 await 一个同步回调注册函数(比如 await some_api(callback=...)),这会报 TypeError: object XXX can't be used in 'await' expression —— 因为那函数本身不是协程,也不返回 Awaitable。
- 别在回调里直接
return,Future需要显式调用.set_result()或.set_exception() - 确保回调执行在事件循环线程内;如果原 API 在其他线程触发回调,要用
asyncio.run_coroutine_threadsafe()转发 - 记得处理异常路径:很多回调 API 会传入
error参数,不能只处理成功分支
用 asyncio.to_thread 包裹阻塞式回调注册?
不推荐。像 some_sdk.register(callback=...) 这类函数,本质是“注册监听器”,不是“立刻执行并返回结果”。用 to_thread 只会让注册动作进线程池,但回调仍发生在原线程,Future 的 set_* 调用可能跨线程,导致事件循环收不到通知。
真正要封装的是“等待一次回调发生”这个动作,而不是“调用注册函数”这个动作。
立即学习“Python免费学习笔记(深入)”;
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 注册操作本身可以同步做(只要它不阻塞)
- 关键是在回调里完成
Future,再由外部await它 - 如果注册函数本身阻塞(如某些 C 库初始化),才考虑
to_thread,但那是另一层问题
如何避免 Future 忘记被 await 导致内存泄漏?
Future 对象不会自动销毁,如果创建了但没人 await 它,又没被引用释放,就会一直挂在那里。尤其在高频调用或异常提前退出时容易出问题。
- 用
try/finally或async with(配合自定义上下文管理器)确保Future最终被消费 - 给
Future加超时:await asyncio.wait_for(fut, timeout=30),避免无限等待 - 不要复用同一个
Future实例多次await—— 它只能完成一次,第二次await会立刻返回结果,可能造成逻辑错乱
实际封装示例:requests + callback 转 async
假设你有个老 SDK 提供 http_get(url, success_cb, error_cb),你想写成 async def async_http_get(url):
import asyncio
<p>async def async_http_get(url):
loop = asyncio.get_running_loop()
fut = loop.create_future()</p><pre class="brush:php;toolbar:false;">def on_success(data):
if not fut.done():
fut.set_result(data)
def on_error(err):
if not fut.done():
fut.set_exception(err)
# 注册回调(同步调用)
http_get(url, on_success, on_error)
return await fut
注意 if not fut.done() 判断:防止 success 和 error 回调都触发、或重复调用时出错。真实场景中还要加日志、取消支持(监听 fut.cancelled() 并清理注册)。
最易被忽略的是回调执行时机与事件循环的耦合——有些 SDK 会立即同步触发回调(比如缓存命中),这时 fut 还没被 await,就得靠 loop.call_soon 延迟到下一轮循环,否则会报 InvalidStateError。

















