news 2026/8/25 22:56:48

Twisted Deferred 与 asyncio 的双向桥接

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Twisted Deferred 与 asyncio 的双向桥接

目录

    • 前言
    • 一、问题的根源:两套不兼容的「可等待」
      • 场景 A:asyncio reactor(Scrapy 2.13+ 的默认值)
      • 场景 B:SelectReactor(传统 Twisted reactor)
      • 对照表
    • 二、`is_asyncio_available()`:所有分支的开关
    • 三、`maybe_deferred_to_future`:Deferred → 可 await
      • `maybe_` 前缀的含义
    • 四、`deferred_from_coro`:协程 → Deferred
      • 两种包装方式的差别
    • 五、`deferred_f_from_coro_f`:装饰器版本
    • 六、`ensure_awaitable`:把任何东西变成可 await
      • `_warn` 参数:一个渐进式废弃的巧思
    • 七、`_schedule_coro`:把协程甩到后台
    • 八、完整的可运行验证脚本
    • 九、在 Spider 里正确使用异步库
      • 前提:确认用的是 asyncio reactor
      • 正确写法
      • 三条铁律
    • 十、报错速查
      • 先确认自己跑在哪种 reactor 上
    • 十一、四个函数的全景
      • 为什么不干脆全换成 asyncio
    • 小结
    • 参考

本文基于Scrapy 2.17.0 + Twisted 26.4.0,所有行为差异都是本机在两种 reactor 下分别实跑验证的。
如果你遇到过RuntimeError: Task got bad yield: <Deferred at 0x...>或者RuntimeError: no running event loop,这篇正好解释根因。

前言

上一篇 拆Scraper时,这几个函数反复出现:

awaitmaybe_deferred_to_future(self.itemproc.process_item(item,spider))returndeferred_from_coro(self.handle_spider_output_async(...))_schedule_coro(self._wait_for_processing(result,request,queue_dfd))returnawaitensure_awaitable(iterate_spider_output(output))

它们都在干同一件事:在 Twisted 的Deferred和 Python 原生的async/await之间来回搬运

Scrapy 的历史包袱决定了它必须同时伺候两套异步体系——底层网络栈是 Twisted,用户代码越来越多用async def。这篇我们看它是怎么把这两个世界缝在一起的。


一、问题的根源:两套不兼容的「可等待」

Twisted 的Deferred和 asyncio 的Future都表示「将来会有的值」,但它们不能互相 await

而更麻烦的是:Scrapy 支持多种 reactor,不同 reactor 下能 await 什么完全相反

我在两种 reactor 下跑了同一段代码,结果是镜像的:

场景 A:asyncio reactor(Scrapy 2.13+ 的默认值)

fromscrapy.utils.reactorimportinstall_reactor install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
is_asyncio_available(): True 1. maybe_deferred_to_future(Deferred) 包装后类型: Future await 结果: 来自 Deferred 的结果 2. 直接 await 裸 Deferred(asyncio reactor 下) 失败: RuntimeError: Task got bad yield: <Deferred at 0x2023d2029c0>

场景 B:SelectReactor(传统 Twisted reactor)

install_reactor("twisted.internet.selectreactor.SelectReactor")
reactor: SelectReactor is_asyncio_available(): False 1. maybe_deferred_to_future(Deferred) 在非 asyncio reactor 下 包装后类型: Deferred <- 原样返回 Deferred await 结果: 结果 2. 直接 await 裸 Deferred 结果: 裸 await 也可以 3. 此时 await asyncio.sleep() 会怎样 失败: RuntimeError: no running event loop

对照表

asyncio reactor非 asyncio reactor
is_asyncio_available()TrueFalse
awaitDeferredTask got bad yield✅ 可以
await asyncio.sleep()✅ 可以no running event loop
maybe_deferred_to_future返回FutureDeferred(原样)

两种环境下能 await 的东西恰好互斥。这就是为什么框架代码里到处都是maybe_deferred_to_future——它是唯一能在两种环境下都工作的写法。


二、is_asyncio_available():所有分支的开关

整个桥接层的每个函数都以它开头:

defis_asyncio_available()->bool:"""Check if it's possible to call asyncio code that relies on the asyncio event loop. .. versionadded:: 2.14 """

文档字符串里定义得很清楚:

This function returnsTrueif there is a running asyncio event loop. If there is no such loop, it returnsTrueif the Twisted reactor that is installed isAsyncioSelectorReactor, returnsFalseif a different reactor is installed, and raises aRuntimeErrorif no reactor is installed.

三种返回情况:

状态返回
有运行中的 asyncio 事件循环True
装了AsyncioSelectorReactorTrue
装了其他 reactorFalse
没装任何 reactorRuntimeError

最后一条要留意:它可能抛异常而不是返回False。在 reactor 安装之前调用桥接函数会直接炸。


三、maybe_deferred_to_future:Deferred → 可 await

defmaybe_deferred_to_future(d:Deferred[_T])->Deferred[_T]|Future[_T]:ifnotis_asyncio_available():returndreturndeferred_to_future(d)defdeferred_to_future(d:Deferred[_T])->Future[_T]:ifnotis_asyncio_available():raiseRuntimeError("deferred_to_future() requires an installed asyncio reactor"" or a running asyncio event loop.")returnd.asFuture(asyncio.get_event_loop())

逻辑简单到不能再简单:

  • asyncio 环境d.asFuture(loop)转成Future
  • 非 asyncio 环境→ 原样返回Deferred

因为在非 asyncio 环境下,Deferred本来就能被await(Twisted 给它实现了__await__)。

maybe_前缀的含义

对比一下这两个函数:

函数非 asyncio 环境下
deferred_to_future抛 RuntimeError
maybe_deferred_to_future原样返回,不报错

maybe_的意思是「可能不转换」。写框架代码或需要跨环境兼容的扩展,永远用maybe_版本;只有当你明确要求 asyncio 环境(比如要 awaitasyncio.Future)时才用不带前缀的版本。

官方文档给的用法示例:

classMySpider(Spider):asyncdefparse
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 22:50:10

基于SpringBoot快递管理系统设计和实现(源码+LW+调试文档)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/8/25 22:48:57

制造业生产质量管理系统的关键要素

摘要&#xff1a;制造业生产质量管理系统是否好用&#xff0c;往往不取决于单一功能&#xff0c;而取决于它能否把标准、过程、数据、反馈和责任闭环起来。本文从标准规范化、过程追溯、数据采集、缺陷闭环、供应商协同、可视化分析等维度&#xff0c;梳理生产质量管理系统的关…

作者头像 李华
网站建设 2026/8/25 22:44:50

北京机器狗供应商避坑指南:2026年最新3大选择标准

最近不少朋友问我机器狗选型的事&#xff0c;说市面上供应商看着都差不多&#xff0c;参数一个比一个漂亮&#xff0c;真到落地就懵了。我接触这个行业五年多&#xff0c;自己也踩过坑&#xff0c;今天聊点实在的&#xff0c;2026年选机器狗供应商&#xff0c;到底该看什么。别…

作者头像 李华
网站建设 2026/8/25 22:40:07

逆向建模关键技术深度解析

三维扫描逆向建模关键技术深度解析&#xff1a;从点云到CAD的"幕后"逆向建模、点云处理、点云配准、网格修复、NURBS曲面拟合、特征识别、CAD重建、参数化逆向建模听起来"一键搞定"&#xff0c;但真要做出高质量的CAD模型&#xff0c;背后有相当多的技术细…

作者头像 李华