bigywater.tech
Django · 112

ASGI 下的同步与异步代码

ASGI 和 WSGI 下的同步、异步代码,分别在哪里执行?这篇在视图里直接打印线程名和事件循环状态,请求真实的 uvicorn / gunicorn 进程,把四种组合的执行位置量出来。

MainThread + 事件循环
ASGI 下 async 视图的执行位置
每请求一个线程
ASGI 下 sync 视图:6 次请求 6 个不同线程
临时事件循环
WSGI 下 async 视图:每个请求在一个线程里临时起一个循环

四种组合

实测 · 本机执行输出
  ASGI (uvicorn)   async def  → 线程 MainThread             是主线程: True  该线程里有运行中的事件循环: True
  ASGI (uvicorn)   def        → 线程 ThreadPoolExecutor-5_0 是主线程: False 该线程里有运行中的事件循环: False
  WSGI (gunicorn)  async def  → 线程 ThreadPoolExecutor-2_0 是主线程: False 该线程里有运行中的事件循环: True
  WSGI (gunicorn)  def        → 线程 MainThread             是主线程: True  该线程里有运行中的事件循环: False

结论只对「执行位置」:ASGI 下 async 视图直接在服务器的事件循环上跑;sync 视图被放到线程里。WSGI 下 sync 视图就是普通的同步调用;async 视图被包了一层,在临时的事件循环里跑完这一个请求(async_to_sync)。

sync 视图每次用的线程

实测 · 本机执行输出
  6 次请求 sync 视图的线程名: ['ThreadPoolExecutor-2_0', 'ThreadPoolExecutor-3_0', 'ThreadPoolExecutor-4_0', 'ThreadPoolExecutor-5_0', 'ThreadPoolExecutor-6_0', 'ThreadPoolExecutor-7_0']
  不同的线程数: 6

线程名里的数字每次都不同,说明每个请求用的是新的线程(而不是共享的一个)。这与下一篇里 sync_to_async 的 thread_sensitive 行为是同一个机制。

分步演示

视图跑在哪
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。uvicorn / gunicorn 各 1 个进程;视图返回线程名与事件循环状态的 JSON
    • 没有验证:线程与请求的对应关系背后的 asgiref 实现细节(本文只观察了现象)、gunicorn gthread / uvicorn 多 worker、中间件里的执行位置(第 116 篇)
    京ICP备2026067502号-1京公网安备11010502063487号