uWSGI线程留存事件循环导致Django SynchronousOnlyOperation异常如何解决
根本原因
- asyncio的事件循环默认和线程绑定,存储在线程的本地上下文里,只要线程不销毁,绑定的事件循环对象就会一直留存。你使用的uWSGI采用常驻工作线程模式,请求处理完成后线程不会退出,会继续接收后续请求。
- 你的
AsyncProvider初始化逻辑会优先获取当前线程已绑定的事件循环,不存在才新建,且全程没有主动解绑线程上下文事件循环的操作。线程1第一次处理/createloop请求时就被绑定了事件循环,后续该线程处理任何请求时,上下文都会携带这个事件循环。 - Django 3.x+的同步ORM会主动检测当前线程是否存在存活状态的事件循环,只要检测到就会判定当前处于异步上下文,禁止直接调用同步ORM接口,抛出
SynchronousOnlyOperation异常。
修复方案
方案1:用完事件循环后主动清理(最适配现有代码)
修改AsyncProvider逻辑,每次使用独立的事件循环,用完后主动关闭并解绑线程上下文,避免循环残留:
import asyncio import aiohttp class AsyncProvider: def __init__(self): # 直接新建独立循环,不获取线程已绑定的循环 self.__loop = asyncio.new_event_loop() self.__session = aiohttp.ClientSession(loop=self.__loop) # 你原有的异步任务执行逻辑 def run_async_task(self, coro): try: return self.__loop.run_until_complete(coro) finally: # 关闭会话和循环 self.__loop.run_until_complete(self.__session.close()) self.__loop.close() # 清除线程绑定的循环,彻底消除残留 asyncio.set_event_loop(None)
方案2:独立线程托管事件循环(高并发场景推荐)
单独开一个常驻线程专门跑事件循环,所有异步操作都提交到该线程执行,完全不污染处理Django请求的工作线程:
import asyncio import threading import aiohttp import time # 全局托管的事件循环和会话 _loop = None _session = None def _run_event_loop(): global _loop _loop = asyncio.new_event_loop() asyncio.set_event_loop(_loop) _loop.run_forever() # 应用启动时启动独立的循环线程 threading.Thread(target=_run_event_loop, daemon=True).start() # 等待循环初始化完成 while _loop is None: time.sleep(0.01) _session = aiohttp.ClientSession(loop=_loop) class AsyncProvider: def run_async_task(self, coro): future = asyncio.run_coroutine_threadsafe(coro, _loop) return future.result()
方案3:临时关闭Django检测(应急用,不推荐长期使用)
如果是老项目临时救急,可以在settings.py中添加配置关闭异步上下文检测,该方案有线程安全风险,仅适合临时过渡:
# settings.py DJANGO_ALLOW_ASYNC_UNSAFE = True
内容的提问来源于stack exchange,提问作者Shubham Dhingra
相关产品推荐
相关产品推荐

