asyncio.run调用motor_asyncio代码报错,loop.run_until_complete却正常
这个问题的核心在于asyncio.run()和手动获取事件循环的循环生命周期管理差异,再加上Motor客户端的循环绑定特性导致的。
为什么两种写法差异这么大?
asyncio.run()的本质是每次调用都会创建一个全新的事件循环,执行完传入的协程后自动关闭这个循环。它是Python 3.7+推出的简化入口,但设计初衷是让整个异步程序的生命周期都在这个新循环内完成。- 而
asyncio.get_event_loop()在主线程中,默认会获取当前已经存在的循环(如果还没有循环则创建一个),并且不会自动关闭它。
你的代码里犯了一个关键错误:在模块级别(最外层代码)初始化了AsyncIOMotorClient。当Python加载这个模块时,就会创建Motor客户端,此时它会自动绑定到当前线程的默认事件循环(也就是模块加载时就存在的那个循环)。
当你用asyncio.run(main())时,它创建了一个新的循环,但你的Motor客户端还绑定在旧的循环上。后续调用users.create_index()时,Motor内部会生成绑定到旧循环的Future,而这个Future被提交到新循环的Task中执行,就触发了"Task got Future attached to a different loop"的错误。
而用get_event_loop() + run_until_complete()时,使用的是和Motor客户端绑定的同一个循环,所有异步操作都在同一个上下文里,自然没问题。
解决方案
最直接且符合asyncio.run()设计理念的修复方式是:不要在模块级别初始化Motor客户端,而是在异步入口函数内部创建。这样客户端会自动绑定到asyncio.run()创建的新循环上。
修改后的代码如下:
import asyncio from motor import motor_asyncio async def main(): # 把客户端初始化移到async函数内部 _client = motor_asyncio.AsyncIOMotorClient() _db = _client.db users = _db.users await users.create_index('login', unique=True) if __name__ == '__main__': asyncio.run(main())
额外说明
asyncio.run()的设计目标是作为异步程序的单一入口,它会全权管理循环的生命周期。所以所有和异步资源相关的初始化(比如数据库客户端、TCP连接、异步队列等)都应该放在这个入口函数内部,或者由入口函数调用的异步函数来初始化,避免出现循环绑定不匹配的问题。
内容的提问来源于stack exchange,提问作者sanyassh

