如何使discord.py机器人永久运行,如果client.run()返回?

4

问题

我的discord.py机器人的client.run()每隔几天就会意外返回,并出现错误“Task was destroyed but it is pending!”。

问题是什么?

为什么client.run()会返回?我该如何更改我的机器人以正确处理此问题并永远运行?

代码(为了讨论而简化):

import asyncio
import discord

TOKEN = 'my bot token goes here'
CHANNEL_ID = 'my channel id goes here'

async def DiscordMsgSendTask():

  await client.wait_until_ready()
  my_channel = discord.Object(id=CHANNEL_ID)

  while not client.is_closed:

    # wait a bit to prevent busy loop
    await asyncio.sleep(2)

    # check for and handle new event here

    # if an event was handled then send a message to the channel
    embed = discord.Embed(description='Event was handled')
    await client.send_message(my_channel, embed=embed)

client = discord.Client()

while True:
  client.loop.create_task(DiscordMsgSendTask())

  try:
    client.run(TOKEN)
  except Exception as e:
    logging.warning('Exception: ' + str(e))

  client = discord.Client()

额外信息

这个机器人的目的基本上就是检查特定目录中是否有新文件,然后发送消息到特定的Discord频道来报告。这只会发生大约10次每天,所以机器人的流量非常低。

为了使机器人容忍错误/断开连接,我将它放入while True循环中,这可能不是正确的方法。

使用Python 3.6.5和Discord.py 0.16.12

编辑-添加traceback

添加先前在一天内发生故障的traceback。

2018-06-20 04:33:08 [ERROR] Task exception was never retrieved
future: <Task finished coro=<WebSocketCommonProtocol.run() done, defined at /usr/local/lib64/python3.6/site-packages/websockets/protocol.py:428> exception=ConnectionResetError(104, 'Connection reset by peer')>
Traceback (most recent call last):
  File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 434, in run
    msg = yield from self.read_message()
  File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 456, in read_message
    frame = yield from self.read_data_frame(max_size=self.max_size)
  File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 511, in read_data_frame
    frame = yield from self.read_frame(max_size)
  File "/usr/local/lib64/python3.6/site-packages/websockets/protocol.py", line 546, in read_frame
    self.reader.readexactly, is_masked, max_size=max_size)
  File "/usr/local/lib64/python3.6/site-packages/websockets/framing.py", line 86, in read_frame
    data = yield from reader(2)
  File "/usr/lib64/python3.6/asyncio/streams.py", line 674, in readexactly
    yield from self._wait_for_data('readexactly')
  File "/usr/lib64/python3.6/asyncio/streams.py", line 464, in _wait_for_data
    yield from self._waiter
  File "/usr/lib64/python3.6/asyncio/selector_events.py", line 723, in _read_ready
    data = self._sock.recv(self.max_size)
ConnectionResetError: [Errno 104] Connection reset by peer
2018-06-20 04:33:08 [ERROR] Task was destroyed but it is pending!
task: <Task pending coro=<DiscordMsgSendTask() running at /home/bot.py:119> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fc99bfd7a68>()]>>
2018-06-20 04:33:08 [ERROR] Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7fc999b59240>

你确定“Task was destroyed but it is pending!”是准确的错误信息吗?我认为它应该有更多的回溯信息。 - Taku
@abccd 增加了更多的输出,包括回溯信息。 - user5071535
1个回答

9
您不能使用预定义的run方法,因为它仅处理KeyboardInterrupt。您需要创建一个退出策略,关闭所有任务但仍保持循环运行。
以下示例将引发SystemExit(模拟意外致命错误(RST-ConnectionError错误)),当机器人收到消息“die”时。 SystemExit将被"捕获"并重新启动您的机器人。在引发KeyboardInterrupt时,机器人将成功退出而无错误。
import asyncio
import discord

TOKEN = 'TOKEN_HERE'
client = discord.Client()


async def task():
    await client.wait_until_ready()
    while True:
        await asyncio.sleep(1)
        print('Running')


def handle_exit():
    print("Handling")
    client.loop.run_until_complete(client.logout())
    # For python 3.9, use asyncio.all_tasks instead
    for t in asyncio.Task.all_tasks(loop=client.loop):
        if t.done():
            t.exception()
            continue
        t.cancel()
        try:
            client.loop.run_until_complete(asyncio.wait_for(t, 5, loop=client.loop))
            t.exception()
        except (asyncio.InvalidStateError, asyncio.TimeoutError, asyncio.CancelledError):
            pass


while True:
    @client.event
    async def on_message(m):
        if m.content == 'die':
            print("Terminating")
            raise SystemExit

    client.loop.create_task(task())
    try:
        client.loop.run_until_complete(client.start(TOKEN))
    except SystemExit:
        handle_exit()
    except KeyboardInterrupt:
        handle_exit()
        client.loop.close()
        print("Program ended")
        break
    
    print("Bot restarting")
    client = discord.Client(loop=client.loop)

在Discord(有人输入die):
die

在终端中(STDOUT):
Running
Running
Running
Terminating
Handling
Bot restarting
Running
Running
Running
Running
Running
<CTRL-C> (KeyboardInterrupt)
Handling
Program ended

小提示:

如果您仍然对错误感到困惑,那么不要责怪您的代码。Errno 104是一种服务器端致命错误,通常无法由最终用户预防。


它可以运行并保持机器人运行,但当我尝试测试“die”消息时,它会引发异常:Traceback (most recent call last): File "C:\python\main.py", line 126, in <module> handle_exit() File "C:\python\main.py", line 31, in handle_exit for t in asyncio.Task.all_tasks(loop=client.loop): AttributeError: type object '_asyncio.Task' has no attribute 'all_tasks' - Windgate
@Windgate 嘿,这是因为你正在使用 py 3.9,asyncio.Task.all_tasks 已经被移动到 asyncio.all_tasks - Taku
今天我尝试使用asyncio.all_tasks,它运行得很好,但在运行时出现了其他错误,我将再次尝试测试并在此处发表评论,感谢你们所有人。 - Windgate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接