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

关于在协程内使用 Uvicorn 无法正常开启 Web 服务的分析处理

关于在协程内使用 uvicorn 无法正常开启 web 的分析处理

1. 问题描述

在使用 asyncio.run() 启动协程时,试图同时启动一个基于 FastAPI 和 Uvicorn 的 Web 服务,然而却遇到了 Web 服务无法正常启动的问题。此问题的根本原因是 asyncio.run()uvicorn.run() 在事件循环上存在冲突。

下面是一个简单的 demo,用于描述这个 bug 的产生,当然,下面这个代码会直接 raise 一个错误,而有些复杂情况可能并不会直观地告诉你具体的错误。

示例代码

import asyncio
import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"message": "Hello, World!"}

async def main():
    # 启动 Web 服务
    uvicorn.run(app, host="0.0.0.0", port=8001)

if __name__ == "__main__":
    asyncio.run(main())

在上述代码中,我们使用 asyncio.run() 启动了 main() 函数,而 main() 函数内部又直接调用了 uvicorn.run() 来启动 Web 服务。运行这个代码时,Web 服务没有正常启动,原因就在于 asyncio.run()uvicorn.run() 都试图管理事件循环,导致冲突。

2. 通过查看 uvicorn 的源码,发现问题根源

查看 uvicorn 的源码可以发现,uvicorn.run() 内部实际上也调用了 asyncio.run() 来启动事件循环。如下:

class Server:
    ...
    def run(self, sockets: list[socket.socket] | None = None) -> None:
        self.config.setup_event_loop()  # 设置事件循环
        return asyncio.run(self.serve(sockets=sockets))  # 使用 asyncio.run 启动事件循环

Server.run() 方法中,asyncio.run() 被用来启动 Web 服务的事件循环,这与我们在 main() 中使用 asyncio.run() 启动事件循环的做法产生了冲突。最终导致 Web 服务无法正常运行。

3. 解决方法

为了避免事件循环的冲突,可以通过将 uvicorn.run() 和主服务封装起来,确保它们共享同一个事件循环。我们可以通过 asyncio.create_task() 来启动 uvicorn 服务,而不是使用 asyncio.run()。这样可以确保两个服务在同一个事件循环中运行。

解决方案代码

import uvicorn
import asyncio
from fastapi import FastAPI

app = FastAPI()

class WebService:
    def __init__(self, host="0.0.0.0", port=8001):
        self.host = host
        self.port = port

    async def run(self):
        """启动 Web 服务"""        
        # 使用 asyncio.create_task 以便让 uvicorn 运行在当前事件循环中
        uvicorn_task = asyncio.create_task(self.run_uvicorn())
        await uvicorn_task

    async def run_uvicorn(self):
        """封装 uvicorn 的启动"""
        config = uvicorn.Config(app, host=self.host, port=self.port)
        server = uvicorn.Server(config)
        await server.serve()

async def main():
    # 创建 Web 服务实例
    web_service = WebService()

    # 启动 Web 服务
    web_service_task = asyncio.create_task(web_service.run())  
    await web_service_task

if __name__ == "__main__":
    asyncio.run(main())

关键点:

  1. 使用 asyncio.create_task() 启动 uvicorn,确保 uvicorn 与主服务共享同一个事件循环。
  2. 封装 uvicorn.run() 代码,通过 async 方式启动 uvicorn,而非直接调用 asyncio.run(),避免了事件循环的冲突。

结论

通过封装 uvicorn 并使用 asyncio.create_task() 启动服务,我们可以确保多个异步服务在同一个事件循环中运行,避免了事件循环冲突问题。这种方法可以顺利启动 Web 服务并避免和其他协程产生冲突。


http://www.kler.cn/a/502562.html

相关文章:

  • 支持Google Analytics快捷添加的CMS:费用与部署形式详解
  • css 布局及动画应用(flex+transform+transition+animation)
  • Windows安装ES单机版设置密码
  • 力扣经典练习题之70.爬楼梯
  • el-table自定义按钮控制扩展expand
  • arcgis中用python脚本批量给多个要素类的相同字段赋值
  • 202409 青少年软件编程等级考试C/C++ 二级真题答案及解析(电子学会)
  • 计算机网络之---IP协议
  • 数据结构二叉树-C语言
  • Windows的Redis查看自己设置的密码并更改设置密码
  • 神经网络中的“池化”是什么意思?
  • MySQL 与 Redis 的数据一致性问题
  • Linux自定义分隔符
  • 【14】模型训练自制数据集前的一些数据处理操作
  • 基于springboot果蔬供应链信息管理平台
  • Linux 下 Vim 环境安装踩坑问题汇总及解决方法(重置版)
  • AI学习路线图-邱锡鹏-神经网络与深度学习
  • 双线性插值算法:原理、实现、优化及在图像处理和多领域中的广泛应用与发展趋势(二)
  • 【数据库】Mysql精简回顾复习
  • 【人工智能】自然语言生成的前沿探索:利用GPT-2和BERT实现自动文本生成与完形填空
  • python-leetcode-长度最小的子数组
  • C#版 软件开发6大原则与23种设计模式
  • 【理论】测试框架体系TDD、BDD、ATDD、MBT、DDT介绍
  • 2025年华为OD上机考试真题(Java)——整数对最小和
  • vulnhub靶场【IA系列】之Keyring
  • 关于Java状态模式的面试题及其答案