Discord 客户端和 Asyncio 多个任务在一段时间后变得疯狂

Discord Client & Asyncio multiple Tasks going all crazy after a while

好的,我有一个 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 个任务,甚至机器人本身 都是随机启动的,而不是根据到每个 task###() 末尾的 await asyncio.sleep(int(cycle))
最奇怪的部分是机器人本身正在触发 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

仍在尝试弄清楚它是否解决了整个问题

如果任务处于阻塞状态(非异步)且耗时过长,则机器人与 Discord 的连接会超时,从用户的角度来看,机器人已离线。当控制返回到机器人的连接逻辑时,它会重新连接到 Discord。重新连接后,它将再次触发 on_ready 事件。

如果需要执行阻塞逻辑,可以参考。要处理每次重新连接时触发 on_ready,您应该在脚本开头设置一个变量 False。然后,在on_ready中检查变量是否为False。如果是,将其设置为 True 并启动模块中的任务。这里的关键点是任务只启动一次。

discord 模块有 tasks 可以用于这样的目的。不建议您为此使用 while 循环。

而是使用您正在使用的 discord 模块中可用的 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))

现在我们已经创建了一个非阻塞循环,这个循环将在机器人上线时启动。

注意 :

我们首先如果任务已经是 运行,如果不是,那么我们才启动它,这是为了防止任务创建自身的多个实例,因为 on_ready 可能会被调用多次。