一段时间后,Discord Client 和 Asyncio 多个任务变得疯狂
Posted
技术标签:
【中文标题】一段时间后,Discord Client 和 Asyncio 多个任务变得疯狂【英文标题】:Discord Client & Asyncio multiple Tasks going all crazy after a while 【发布时间】:2022-01-14 07:12:16 【问题描述】:好吧,我有一个 Discord 机器人,在 main.py
中声明如下:
import discord
import asyncio
#
import settingsDB
import logger
#
import module_one
import module_two
import module_three
import module_four
[...]
client = discord.Client()
botToken = settingsDB.getBotSetting("DiscordToken")
# on_ready event stuff
@client.event
async def on_ready():
# stuff
# [...]
# on_message event stuff
@client.event
async def on_message(message):
# stuff
# [...]
# Tasks functions
async def taskOne():
while True:
cycle = settingsDB.getBotSetting("taskOne_Cycle")
# calling for stuff in module_one.py
# stuff
# [...]
await asyncio.sleep(int(cycle))
async def taskTwo():
while True:
cycle = settingsDB.getBotSetting("taskTwo_Cycle")
# calling for stuff in module_two.py
# stuff
# [...]
await asyncio.sleep(int(cycle))
async def taskThree():
while True:
cycle = settingsDB.getBotSetting("taskThree_Cycle")
# calling for stuff in module_three.py
# stuff
# [...]
await asyncio.sleep(int(cycle))
async def taskFour():
while True:
cycle = settingsDB.getBotSetting("taskFour_Cycle")
# calling for stuff in module_four.py
# stuff
# [...]
await asyncio.sleep(int(cycle))
client.run(botToken)
settingsDB
指的是settingsDB.py
,其中存储了所有查询我的数据库的函数,并且可以根据通过与 Discord bot 无关的另一个源(通过网站 php)更新的用户输入进行更改。
logger
指的是logger.py
,我正在将我想要作为日志的内容写入 txt 文件。
在on_ready
事件下我写道:
@client.event
async def on_ready():
print('Logged in as 0.user'.format(client))
try:
asyncio.ensure_future(taskOne())
asyncio.ensure_future(taskTwo())
asyncio.ensure_future(taskThree())
asyncio.ensure_future(taskFour())
except BaseException as err:
logger.logger('Unexpected error > "' + str(err) + '" / "' + str(type(err)) + '"')
raise
在前几天我已经多次更改这部分,因为我想要真正的异步行为,但无法实现。
但无论我在这里写什么,我注意到几个小时后,4 个任务,甚至机器人本身都是随机启动的,而不是根据await asyncio.sleep(int(cycle))
每个task###()
的末尾都有。
最奇怪的部分是机器人本身正在触发print
行,因此告诉我它再次登录(???)
我不得不提一下,taskOne()
可以根据它处理的内容而变化很大,可以从 1 分钟到近 20 分钟不等。
任何想法为什么它会这样? 如果您需要更多详细信息,请告诉我。
在@PythonPro 推荐后,我更改了on_ready
函数:
G_hasLaunched = False
@client.event
async def on_ready():
global G_hasLaunched
print('Logged in as 0.user'.format(client))
if G_hasLaunched == False:
G_hasLaunched = True
try:
print("creating tasks")
asyncio.ensure_future(taskOne())
asyncio.ensure_future(taskTwo())
asyncio.ensure_future(taskThree())
asyncio.ensure_future(taskFour())
except BaseException as err:
logger.logger('Unexpected error > "' + str(err) + '" / "' + str(type(err)) + '"')
raise
仍在试图弄清楚它是否解决了整个混乱
【问题讨论】:
我想提供一些有关何时执行on_ready()
的上下文,它在机器人首次启动或网络中断并且机器人尝试重新连接时执行。那么,是不是发生在网络中断之后呢?
用asyncio.create_task
代替asyncio.ensure_future
怎么样?
您从数据库中获取的数据也是同步的。他们可以阻止事件循环。
@Ratery 起初我使用client.loop.created_task
然后我尝试asyncio.create_task
,同样的行为:(
【参考方案1】:
如果任务阻塞(非异步)并且花费的时间过长,则机器人与 Discord 的连接会超时,并且从用户的角度来看,机器人已离线。当控制权返回到机器人的连接逻辑时,它会重新连接到 Discord。重新连接后,它将再次触发on_ready
事件。
如果需要执行阻塞逻辑,可以参考this *** answer。要处理每次重新连接时触发的on_ready
,您应该在脚本开头有一个变量设置为False
。然后,在on_ready
中,检查变量是否为False
。如果是,请将其设置为 True
并启动模块中的任务。这里的关键是任务只启动一次。
【讨论】:
所以,我添加了一个G_hasLaunched
布尔值,它似乎已经连续工作了 7 个小时。直到发生与此无关的错误。我用CTRL+C
强制退出脚本,等待机器人在 Discord 应用程序中进入离线状态。然后再次启动mail.py
,但随后,print('Logged in as [...])
在不到 1000 万的时间内出现了 2 次......我不知道为什么......我检查了ps aux | grep .py
除了我自己的grep
命令,没有出现过, /usr/bin/python3 /usr/bin/fail2ban-server -xf start
和 /usr/bin/python2 /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf
.
到目前为止,最后一次测试持续了 21 小时,直到我不得不强制退出以在我的应用程序中进行编辑。它现在似乎有效。我会等几天,然后接受您的回复作为解决方案。
您的机器人可能由于网络中断而重新连接。这很正常,我建议的解决方案应该确保只启动一组任务,无论 on_ready 被触发多少次。【参考方案2】:
discord
模块有tasks
可以用于完全类似的目的。不建议您为此使用while
循环。
请改用您正在使用的不和谐模块中的tasks.loop()
。
请注意,这必须从discord.ext
手动导入
所以它看起来像这样:
from discord.ext import tasks
@bot.event
async def on_ready():
if not taskOne.is_running():
taskOne.start()
# Do this for other tasks as well
@tasks.loop(seconds=0)
async def taskOne():
# Do your things here and write other tasks that you want run in background
cycle = settingsDB.getBotSetting("taskOne_Cycle")
# calling for stuff in module_one.py
# stuff
# [...]
await asyncio.sleep(int(cycle))
现在我们已经创建了一个非阻塞循环,这个循环会在bot上线时启动。
请注意:
如果任务已经在运行,我们首先启动它,如果它不是,那么我们将启动它,这是为了防止任务创建自身的多个实例,因为on_ready
可能会被多次调用。
【讨论】:
在第一个版本中,我使用了任务,但我改变了这个,因为我不能用变量改变循环。您的解释中有一些我不明白的地方 >@tasks.loop(seconds=0)
和 await asyncio.sleep(int(cycle))
。每个任务可以有一个不同的cycle
。以上是关于一段时间后,Discord Client 和 Asyncio 多个任务变得疯狂的主要内容,如果未能解决你的问题,请参考以下文章
Discord.js client.addMemberToRole 不工作
client.startTyping 不是函数 (Discord.JS)