Python子线程运行asyncio报无当前事件循环错误如何排查
问题现象
运行异步代码时抛出报错:RuntimeError: There is no current event loop in thread 'Thread-1'。
现有公开资料显示,该错误的常见触发场景为在非协程上下文中直接调用事件循环的run_until_complete方法,示例写法如下:
loop.run_until_complete(future)
但出现问题的代码并未使用上述写法,其异步批量下载函数实现如下:
async def downloadAll(urls:dict, lastSet:bool, outfile): my_conn = TCPConnector(limit=20) async with ClientSession(connector=my_conn) as session: tasks = [] for j, item in enumerate(urls): for id_, url in item.items(): task = asyncio.ensure_future(downloadLink(url,id_,outfile,session)) tasks.append(task) print("running async requests...") return await asyncio.gather(*tasks,return_exceptions=True)
上述函数运行时仍然抛出前述事件循环不存在的错误,结合Python官方文档“常规异步使用场景下应避免修改顶层以外的async相关属性”的说明,可按以下路径排查解决。
排查与解决方法
- 先定位报错的线程上下文:错误信息明确提示异常出现在
Thread-1而非主线程,Python 3.8及以上版本中,子线程默认不会自动创建绑定事件循环,asyncio.ensure_future在没有运行中事件循环的线程内被调用时,会主动尝试获取当前线程的事件循环,获取失败就会抛出该错误,和是否显式调用run_until_complete没有直接关联。 - 修正任务创建逻辑:不要在事件循环未正式运行时提前用
asyncio.ensure_future封装任务,直接将协程对象传入asyncio.gather即可,修改后的下载函数代码如下:
async def downloadAll(urls:dict, lastSet:bool, outfile): my_conn = TCPConnector(limit=20) async with ClientSession(connector=my_conn) as session: tasks = [] for j, item in enumerate(urls): for id_, url in item.items(): # 直接追加协程对象,无需提前封装为Future tasks.append(downloadLink(url,id_,outfile,session)) print("running async requests...") return await asyncio.gather(*tasks,return_exceptions=True)
- 修正子线程的异步启动逻辑:如果确实需要在子线程中运行异步下载逻辑,必须在子线程的执行入口内显式创建、绑定事件循环,禁止跨线程传递事件循环对象,子线程启动异步逻辑的标准写法如下:
def download_thread_entry(urls, lastSet, outfile): # 子线程内新建事件循环并绑定为当前线程的上下文loop loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: # 启动异步入口函数 return loop.run_until_complete(downloadAll(urls, lastSet, outfile)) finally: # 执行完成后关闭循环释放资源 loop.close()
- 规避版本兼容问题:主线程启动异步逻辑统一使用
asyncio.run()作为唯一入口,不要手动调用get_event_loop()、修改全局事件循环策略,避免因不同Python小版本的事件循环逻辑差异触发异常。
内容的提问来源于stack exchange,提问作者Sim Borodach
相关产品推荐
相关产品推荐

