一段时间后,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 本地图像嵌入

Discord.js client.addMemberToRole 不工作

client.startTyping 不是函数 (Discord.JS)

需要帮助在 discord.js 中完成一个单词 cencor [关闭]

反应后 Discord.js 编辑消息