当前位置: 首页 > news >正文

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
http://www.cnnetsun.cn/news/4216354.html

相关文章:

  • 【会议征稿通知 | 郑州轻工业大学主办 | JPCS出版 | EI 、Scopus稳定检索】第三届航空航天、机械与材料工程国际学术会议 (AMME 2026)
  • 基于SpringBoot快递管理系统设计和实现(源码+LW+调试文档)
  • 制造业生产质量管理系统的关键要素
  • 【会议征稿通知 | 重庆邮电大学、重庆蚂蚁消费金融有限公司主办 | IEEE出版 | EI 、Scopus稳定检索】第一届粒球计算国际会议(ICGBC 2026)
  • 北京机器狗供应商避坑指南:2026年最新3大选择标准
  • 逆向建模关键技术深度解析
  • 调度主管的一天
  • 前端该重视的编程思想
  • FPGA SoC 的 RISC-V 固件开发全攻略(七):Bootloader 设计详解
  • 时事解读:Moderna大涨背后:AI到底站在哪一步?——从intismeran autogene到上海生物制品研究所:新抗原筛选这一步,为什么非AI不可
  • 北美红雪松:一种连接建筑、自然与时间感的天然材料
  • 数据恢复软件会损坏原盘吗?正确操作避坑
  • 抖音批量下载工具 douyin-downloader:3 步跑通批量下载,附增量更新与避坑指南
  • 鸿蒙 ArkTS 实战|用卡片组件重构化合反应学习
  • AI科研绘图工具实测对比:哪款更适合论文配图
  • 课本同步英语听力工具怎么选?2026年避坑3要点
  • 抖音批量下载工具完整教程:三步命令跑通批量无水印归档
  • 验收结算:草率签字等于防线失守
  • 弯管专用关节臂式三坐标:如何高效解决管件测量难题
  • 变压器的“数字医生”:蜂窝通信的云控终端
  • opencode 配置完全指南:配置文件、目录与字段详解
  • 计算机毕业设计之后台管理系统设计
  • 如何判断货物要不要做 ISTA‑6A(ISTA 6‑Amazon‑SIOC)
  • 2026年跨境电商入局必看:TikTok Shop美区5条合规新规,踩中一条就被限流封店
  • 告别 Copilot?Codex 本地化部署指南:从原理到实战
  • 次世代二次元游戏角色全流程制作:从Blender建模到Unity引擎实战
  • 每一次Flash升级,都是一次产线的“重新高考“
  • 联软科技推出UniNDR,打通终端、服务器与网络侧的威胁检测链路
  • 云原神 PC 客户端新增完整支持:低配玩家怎么更新?
  • Redis 从了解到精通(四・下):分区技术原理与选型全解析