bigywater.tech
Django · 114

SynchronousOnlyOperation 产生原因

第一次在 async 视图里写 ORM,大多会撞上 SynchronousOnlyOperation。这篇把它出现的条件、几种正确写法、以及那个「关掉检查」的环境变量分别跑出来。

SynchronousOnlyOperation
当前线程里有运行中的事件循环时调用同步 ORM 抛出
a 前缀 / async for
异步 ORM 的写法
DJANGO_ALLOW_ASYNC_UNSAFE
只关检查,查询仍会阻塞事件循环

直接调用会怎样

实测 · 本机执行输出
  Item.objects.count()             → SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
  list(Item.objects.all())         → SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
  Item.objects.create(name="x")    → SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
  connection.cursor()              → SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

不是只有查询才会触发:connection.cursor() 也一样。它检查的是「当前线程里有没有正在运行的事件循环」,而不是「这个函数是不是 async def」。

正确写法

实测 · 本机执行输出
  await Item.objects.acount() → 0
  await sync_to_async(Item.objects.count)() → 0
  [i async for i in Item.objects.all()] → 0 行

DJANGO_ALLOW_ASYNC_UNSAFE

实测 · 本机执行输出
  在 async 里直接 count() → 0

官方留它是为了 Jupyter 这类环境。在服务里打开它,只会让你失去这个提醒,同步查询照样阻塞整个事件循环里的所有请求。

在真实服务里

实测 · 本机执行输出
  /bad/  状态码 500  页面标题: SynchronousOnlyOperation at /bad/
  /good/ 状态码 200  内容: 0
  检查用的装饰器(读源码): django/utils/asyncio.py 第 8 行:def async_unsafe(message):

分步演示

SynchronousOnlyOperation
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;asyncio.run 里的脚本 + 1 个 uvicorn 进程
    • 没有验证:「阻塞事件循环」的实际延迟影响(本文只演示报错与开关)、Jupyter 场景、其它会触发 async_unsafe 检查的操作清单
    京ICP备2026067502号-1京公网安备11010502063487号