如何在同步实例方法中调用Discord异步消息发送方法?
问题概述
同步方法send_message()在线程中调用,需要触发Discord的异步方法my_channel.send(),尝试asyncio.run、async_to_sync等多种实现方式后,先后遇到两类错误:
TypeError: type of the return value must be str; got coroutine insteadRuntimeError: Timeout context manager should be used inside a task
相关代码
#@typechecked class MessageCog(Cog): @typechecked async def asend_message(self, message:str)->str: my_channel:TextChannel = self.bot.get_channel(self.channel) assert isinstance(my_channel, TextChannel) embed :Embed = Embed(title="Response", description=message, color=pepe_green) assert isinstance(embed, Embed) my_message:Message = await my_channel.send(embed=embed) assert isinstance(my_message, Message) result :str = str(my_message) # ?? assert isinstance(result, str) if not isinstance(result, str): raise Exception() return result @typechecked def send_message(self, message:str)->str: # #f:Coroutine = lambda m: self.asend_message(m) #result:str = run(f) # #result:str = run(self.asend_message(message)) # #result:str = get_running_loop().run_untilcomplete(self.asend_message(message)) # #f:Callable[[str],str] = async_to_sync(MessageCog.asend_message) # #f = async_to_sync(MessageCog.asend_message) #result:str = f(self, message) # #result:str = create_task(self.asend_message(message)) # #f = partial(MessageCog.asend_message, self) #result:str = run(f(message)) assert isinstance(result, str) if not isinstance(result, str): raise Exception() return result
错误信息
初始错误
[chain/error] [chain:AgentExecutor] [82.96s] Chain run errored with error: ... TypeError: type of the return value must be str; got coroutine instead
更新后错误
[tool/error] [chain:AgentExecutor > tool:send_message] [3ms] Tool run errored with error: RuntimeError('Timeout context manager should be used inside a task')Traceback (most recent call last): File "/home/user/.local/lib/python3.10/site-packages/langchain_core/tools.py", line 409, in run context.run( File "/home/user/.local/lib/python3.10/site-packages/langchain_core/tools.py", line 750, in _run else self.func(*args, **kwargs) File "/home/user/enumerate_bot/./eris.py", line 380, in <lambda> func =lambda message: self. send_message(message), File "/usr/lib/python3/dist-packages/typeguard.py", line 494, in wrapper retval = func(*args, **kwargs) File "/home/user/enumerate_bot/./eris.py", line 335, in send_message result:str = run(g) File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run return loop.run_until_complete(main) File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete return future.result() File "/home/user/enumerate_bot/./eris.py", line 306, in asend_message my_message:Message = await my_channel.send(embed=embed) File "/home/user/.local/lib/python3.10/site-packages/discord/abc.py", line 1618, in send data = await state.http.send_message(channel.id, params=params) File "/home/user/.local/lib/python3.10/site-packages/discord/http.py", line 638, in request async with self.__session.request(method, url, **kwargs) as response: File "/home/user/.local/lib/python3.10/site-packages/aiohttp/client.py", line 1197, in __aenter__ self._resp = await self._coro File "/home/user/.local/lib/python3.10/site-packages/aiohttp/client.py", line 507, in _request with timer: File "/home/user/.local/lib/python3.10/site-packages/aiohttp/helpers.py", line 715, in __enter__ raise RuntimeError( RuntimeError: Timeout context manager should be used inside a task
解决方案
核心原因
- 直接返回未执行的协程对象会触发
TypeError,因为同步方法要求返回str类型,而协程不是字符串。 asyncio.run会创建全新的事件循环,但Discord的HTTP客户端绑定在bot的原循环中,新循环无法兼容aiohttp的超时上下文,导致RuntimeError。create_task仅能将任务提交到当前运行的循环,但同步线程中可能没有活跃循环,或无法直接获取执行结果。
正确实现代码
使用Discord.py自带的async_to_sync工具,它会自动处理循环切换和线程安全问题,适配Discord内部上下文:
from discord.ext.commands import async_to_sync # ... 其他代码保持不变 ... @typechecked def send_message(self, message:str)->str: # 用async_to_sync包装异步方法 sync_send = async_to_sync(self.asend_message) result = sync_send(message) assert isinstance(result, str) return result
关键注意事项
- 必须导入
discord.ext.commands下的async_to_sync,而非第三方库的同名工具,它专门适配Discord.py的线程和循环模型。 - 禁止在同步线程中创建新事件循环(如
asyncio.run),必须复用bot已有的事件循环。 - 即使
send_message在非bot主线程中调用,async_to_sync也会自动完成线程间的循环切换,确保操作在bot的事件循环中执行。
内容的提问来源于stack exchange,提问作者Innovations Anonymous
相关产品推荐
相关产品推荐

