uvicorn.run() 是最直接的启动方式,等价于命令行 uvicorn main:app,但会阻塞线程;在已有 asyncio 环境中需用 uvicorn.Server 配合 await server.serve() 实现非阻塞并发启动。

直接调用 uvicorn.run() 启动服务
最常见、最直接的方式是导入 uvicorn 并调用其 run() 函数,这等价于执行命令行 uvicorn main:app。它会阻塞当前线程,启动异步事件循环和 HTTP 服务器。
典型用法:
import uvicorn
<p>if <strong>name</strong> == "<strong>main</strong>":
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
</p>
注意几个关键点:
-
"main:app"是模块名加应用实例名,中间用英文冒号分隔;路径错误会导致ImportError: cannot import name 'app'或ModuleNotFoundError -
reload=True仅在开发时启用,依赖watchfiles(新版 Uvicorn)或watchdog,生产环境必须设为False - 若在 Jupyter、pytest 或已有 asyncio event loop 的环境中调用,会报
RuntimeError: asyncio.run() cannot be called from a running event loop
在已有 asyncio 环境中非阻塞启动
当你需要把 Uvicorn 集成进一个已运行的 asyncio 应用(比如同时跑 WebSocket 服务、定时任务或后台协程),就不能用 uvicorn.run() —— 它会自己调用 asyncio.run() 并接管整个 loop。
此时应使用 uvicorn.Server 类手动构建并启动:
import asyncio
import uvicorn
<p>config = uvicorn.Config("main:app", host="127.0.0.1", port=8000, loop="asyncio")
server = uvicorn.Server(config)</p><h1>在已有 loop 中作为 task 运行</h1><p>async def main():
await server.serve()</p><p>asyncio.run(main())
</p>
关键区别:
-
uvicorn.Server不自动启动 loop,只封装了生命周期管理 -
server.serve()是协程函数,可 await,支持与其它 task 并发(如asyncio.gather(server.serve(), run_background_job())) -
loop="asyncio"是显式声明策略,避免旧版默认用uvloop导致兼容问题
绕过命令行解析,传参更灵活
用 uvicorn.run() 时,很多参数(如 --ssl-keyfile)只能靠字符串传入,难做动态拼接;而通过 uvicorn.Config 实例构造,所有配置都可编程控制。
例如动态加载证书路径、根据环境切换日志级别:
import os
from uvicorn.config import Config
from uvicorn.server import Server
<p>cert_path = os.getenv("SSL_CERT", "")
key_path = os.getenv("SSL_KEY", "")</p><p>config = Config(
app="main:app",
host="0.0.0.0",
port=int(os.getenv("PORT", "8000")),
ssl_keyfile=key_path if key_path else None,
ssl_certfile=cert_path if cert_path else None,
log_level="info" if os.getenv("DEBUG") else "warning",
)
server = Server(config)
await server.serve()
</p>
容易忽略的细节:
Uvicorn 0.52.1 是 Python ASGI Web 服务器,提供异步应用服务及 WebSocket 支持,常用于 FastAPI、Starlette 等应用的开发和部署。本下载为官方 PyPI 通用 wheel 包,可使用 pip 安装,并由包管理器处理所需依赖。
-
ssl_keyfile和ssl_certfile必须**同时存在且路径有效**,否则启动失败并抛出OSError: [Errno 2] No such file or directory -
log_level只影响 Uvicorn 自身日志,不影响 FastAPI 的logger输出 -
Config构造时不做路径校验,错误要等到server.serve()才暴露
多进程启动需额外处理 fork 时机
Uvicorn 命令行支持 --workers,但用编程方式启动时,不能简单地在主进程中多次调用 uvicorn.run() —— 这会导致端口冲突或子进程无法正确继承 event loop。
正确做法是让主进程 fork 后,由每个子进程单独调用 uvicorn.run(),且确保 fork 发生在 asyncio loop 创建之前:
import multiprocessing as mp import uvicorn <p>def run_worker(app_module, host, port):</p><h1>每个 worker 独立运行,不共享 loop</h1><pre class="brush:php;toolbar:false;">uvicorn.run(app_module, host=host, port=port, workers=1)
if name == "main":
必须在 if name == "main": 下启动,Windows 兼容必需
processes = []
for i in range(2):
p = mp.Process(target=run_worker, args=("main:app", "127.0.0.1", 8000 + i))
p.start()
processes.append(p)
for p in processes:
p.join()
坑点很实在:
- Windows/macOS 上必须用
if __name__ == "__main__":包裹多进程代码,否则反复 fork -
uvicorn.run()内部会调用os.fork()(Unix)或 spawn(Windows),若你在 fork 后再创建 loop,会触发RuntimeError: There is no current event loop in thread - 多个 worker 绑定同一端口会失败,需分配不同
port或用SO_REUSEPORT(需内核支持,Uvicorn 默认不启用)
Uvicorn 的编程式启动看似简单,真正麻烦的是它和 asyncio、multiprocessing、SSL 初始化之间的耦合点——这些地方不报错则已,一错就是静默挂起或进程僵死。动手前先确认你的运行上下文:是不是已有 loop?要不要热重载?是否跨平台?这些决定了该走哪条路径。










