使用Asyncio与Uvicorn ASGI Server时队列任务报错问题排查
问题分析与解决
错误根源
这个RuntimeError的核心是事件循环不匹配:
- 你在全局作用域创建
queue = asyncio.Queue(1)时,队列会自动绑定到当前的默认事件循环 - 但之后你手动创建了新的事件循环
loop = asyncio.new_event_loop(),并将其传给Uvicorn的Config,同时在这个新循环中启动了add任务 - 当队列满(容量为1)或空时,队列内部用于等待的Future属于旧的默认循环,而
add任务运行在新循环中,两者冲突就抛出了"Future attached to a different loop"的异常
修复方案
方案1:让队列绑定到新事件循环
把队列的创建放到新循环的上下文里,确保队列、任务、服务器使用同一个事件循环:
import asyncio from uvicorn import Config, Server import random async def add(queue): while True: await queue.put(str(random.randint(1, 10000))) print(queue.qsize()) await asyncio.sleep(1) async def app(scope, receive, send): await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ] }) val = await queue.get() queue.task_done() await send({ 'type': 'http.response.body', 'body': bytes(val, 'utf-8'), }) loop = asyncio.new_event_loop() # 将新循环设为默认循环,再创建队列,确保队列绑定到该循环 asyncio.set_event_loop(loop) queue = asyncio.Queue(1) loop.create_task(add(queue)) config = Config(app=app, loop=loop) server = Server(config) loop.run_until_complete(server.serve())
方案2:让Uvicorn管理事件循环(更简洁推荐)
不需要手动创建和维护事件循环,交给Uvicorn自动处理,只需在启动前把add任务加入Uvicorn使用的循环即可:
import asyncio from uvicorn import Config, Server import random queue = asyncio.Queue(1) async def add(queue): while True: await queue.put(str(random.randint(1, 10000))) print(queue.qsize()) await asyncio.sleep(1) async def app(scope, receive, send): await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ] }) val = await queue.get() queue.task_done() await send({ 'type': 'http.response.body', 'body': bytes(val, 'utf-8'), }) async def main(): # 在主协程中创建任务,自动绑定到Uvicorn使用的事件循环 asyncio.create_task(add(queue)) config = Config(app=app) server = Server(config) await server.serve() if __name__ == "__main__": asyncio.run(main())
为什么方案2更好?
Uvicorn会自动处理事件循环的创建、适配和销毁,完全符合asyncio的最佳实践,避免手动管理循环时容易出现的绑定错误,代码结构也更清晰。
内容的提问来源于stack exchange,提问作者Vorteg Anum
相关产品推荐
相关产品推荐

