Discord.py循环发消息时遇连接错误与心跳阻塞问题求助
问题
我写了下面的脚本,想每次进入IF分支时给Discord频道发消息。脚本确实能在循环里发消息,但等待再次进入IF分支的过程中(预期每60秒一次)出现了错误和警告。
脚本内容
import discord import asyncio TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXX' channel_id = 111111111111111111111111 intents = discord.Intents.default() intents.typing = False intents.presences = False client = discord.Client(intents=intents) @client.event async def on_ready(): print(f'We have logged in as {client.user}') await send_message_in_loop() async def send_message_in_loop(): channel = client.get_channel(channel_id) while True: try: while True: # ================================================== # DOING SOME STUFF HERE INCLUDING SOME "time.sleep" # ================================================== passedTime=SERVERTIME # the server time is being updated by server API # I want to enter the loop when the seconds of the server time are within 3 to 6 if (passedTime)>3 and (passedTime)<6: # ================================================== # DOING SOME STUFF HERE INCLUDING SOME "time.sleep" # ================================================== message='last check: '+str(SERVERTIME) task = asyncio.get_event_loop().create_task(channel.send(message)) await asyncio.gather(task) await asyncio.sleep(5) except: print('repeat') client.run(TOKEN)
出现的错误
ERROR discord.client Attempting a reconnect in 0.59s Traceback (most recent call last): File "C:\Users\lt13485\AppData\Local\Programs\Python\Python311\Lib\site-packages\discord\client.py", line 659, in connect await self.ws.poll_event() File "C:\Users\lt13485\AppData\Local\Programs\Python\Python311\Lib\site-packages\discord\gateway.py", line 646, in poll_event raise ConnectionClosed(self.socket, shard_id=self.shard_id, code=code) from None discord.errors.ConnectionClosed: Shard ID None WebSocket closed with 1000
收到的警告
WARNING discord.gateway Shard ID None heartbeat blocked for more than 10 seconds. Loop thread traceback (most recent call last):
我希望终端不再出现这些错误,因为还要在终端打印其他内容。
解决方案
问题核心是异步代码里使用了同步阻塞的time.sleep(),这个函数会卡死整个asyncio事件循环,导致Discord客户端无法按时发送心跳包,最终触发连接断开和警告。
修复步骤
替换所有
time.sleep()为await asyncio.sleep()time.sleep()是同步函数,会让事件循环完全停转;asyncio.sleep()是异步非阻塞的,会把控制权交还给事件循环,保证Discord的心跳任务正常执行。
简化消息发送逻辑
- 不需要手动创建Task再用gather,直接
await channel.send(message)即可,代码更简洁高效。
- 不需要手动创建Task再用gather,直接
优化循环结构
- 原代码的双层
while True属于冗余逻辑,改成单层循环降低复杂度,同时添加短时间的异步睡眠,避免频繁检查占用资源。
- 原代码的双层
修复后的示例代码
import discord import asyncio TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXX' channel_id = 111111111111111111111111 intents = discord.Intents.default() intents.typing = False intents.presences = False client = discord.Client(intents=intents) @client.event async def on_ready(): print(f'We have logged in as {client.user}') await send_message_in_loop() async def send_message_in_loop(): channel = client.get_channel(channel_id) while True: try: # ================================================== # 替换这里的time.sleep(x)为await asyncio.sleep(x) # ================================================== passedTime = SERVERTIME # 由服务器API更新的时间 # 当服务器时间的秒数在3-6之间时执行 if 3 < passedTime < 6: # ================================================== # 同样替换这里的time.sleep(x)为await asyncio.sleep(x) # ================================================== message = f'last check: {SERVERTIME}' await channel.send(message) await asyncio.sleep(5) # 给事件循环留处理心跳的时间,避免高频检查 await asyncio.sleep(0.1) except Exception as e: print(f'出错了: {e}') # 打印具体异常,方便排查,不要用空except client.run(TOKEN)
额外注意事项
- 永远不要在asyncio异步函数中使用同步阻塞函数(比如
time.sleep()、requests.get()),如果必须用同步代码,要放到asyncio.to_thread()里执行。 - 空
except会捕获所有异常(包括终止程序的KeyboardInterrupt),不利于调试,建议捕获具体异常类型,或至少打印异常信息。
内容的提问来源于stack exchange,提问作者Neuroscientist
相关产品推荐
相关产品推荐

