discord.py Timeout context manager需在task内运行报错求助
问题描述
我最初以为Timeout context manager should be used inside a task这个报错是因为代码没有运行在async def定义的异步函数内,但我的相关逻辑确实都写在异步函数里。我先执行了loop = asyncio.new_event_loop()创建事件循环,之后通过loop.run_until_complete(onInfoAsync(info,conn))执行异步逻辑,具体报错信息如下:
Timeout context manager should be used inside a task File "C:\Users\ianfi\Documents\pythonStuff\PyQt5\ServerCalling\main.py", line 42, in onInfoAsync dm = await member.create_dm() File "C:\Users\ianfi\Documents\pythonStuff\PyQt5\ServerCalling\main.py", line 57, in onInfo loop.run_until_complete(onInfoAsync(info,conn))
从报错栈可以看到,错误最终追溯到loop.run_until_complete(onInfoAsync(info,conn))调用处,按我的理解这个调用本来应该把协程包装成任务运行,暂时没找到问题根源。我是Stack Overflow新用户,如果排查需要更多信息可以随时告知。
我已经尝试过以下解决方案:
- 网上有方案提出用
asyncio.get_event_loop()替代手动新建事件循环的逻辑可以修复问题,但实际执行时报错提示当前没有可用的事件循环可以获取 - 尝试调用
asyncio.set_event_loop(loop)绑定新建的事件循环,问题没有解决
感谢大家的帮助!
问题补充
后续我做了进一步尝试,编写了一个中间协程函数:
async def between(info:str, conn): task = asyncio.create_task(onInfoAsync(info,conn)) await task
即便已经显式通过asyncio.create_task创建任务(和报错提示要求的“逻辑运行在任务内”一致),错误仍然存在,使用该中间函数后的报错栈如下:
Timeout context manager should be used inside a task File "C:\Users\ianfi\Documents\pythonStuff\PyQt5\ServerCalling\main.py", line 51, in onInfoAsync dm = await member.create_dm() File "C:\Users\ianfi\Documents\pythonStuff\PyQt5\ServerCalling\main.py", line 36, in between await task File "C:\Users\ianfi\Documents\pythonStuff\PyQt5\ServerCalling\main.py", line 75, in onInfo loop.run_until_complete(between(info,conn))
问题原因
这个报错是discord.py库内部超时上下文的校验逻辑导致的:loop.run_until_complete()直接接收协程作为参数时,传入的入口协程不会被自动包装为asyncio.Task实例,只是作为裸协程在事件循环中启动运行。member.create_dm()内部的超时管理器会校验当前执行上下文是否处于Task环境,你在协程内部调用asyncio.create_task()创建的子任务虽然是合法Task实例,但最外层执行上下文始终是裸协程,校验自然不通过,套一层中间协程也没用,和是否手动绑定事件循环、用不用get_event_loop没有直接关系。
修复方法
不要直接把业务协程传给run_until_complete,显式用loop.create_task()把入口协程包装成Task实例再传入即可,不需要额外写中间协程。
修改你的调用代码:
loop = asyncio.new_event_loop() # 必须先把新创建的循环绑定到当前线程,否则后续asyncio接口调用会出问题 asyncio.set_event_loop(loop) # 显式将入口协程包装为Task entry_task = loop.create_task(onInfoAsync(info, conn)) loop.run_until_complete(entry_task)
如果你要保留between中间层的写法,只需要把传给run_until_complete的对象换成loop.create_task(between(info,conn))即可,效果一致。
补充说明
你之前调用asyncio.get_event_loop()报错,是因为PyQt5主线程默认没有初始化asyncio事件循环,手动创建新循环后只要先执行asyncio.set_event_loop(loop)把循环绑定到当前线程,后续再调用asyncio.get_event_loop()就能正常获取到这个循环实例,不会再抛错。
内容的提问来源于stack exchange,提问作者ianfinity

