在异步事件循环中制作简单加载图标的问题

Issues with making a simple loading icon inside an async event loop

我正在为 discord 服务器制作一个机器人,并且有一个功能需要一些时间才能 运行。我想像这样 Doing something: <spinning icon> 在状态消息旁边添加一个旋转加载图标。它编辑原始消息以遍历这些消息:

Doing something: \ Doing something: | Doing something: / Doing something: -

我尝试使用单独的线程来更新消息,如下所示:

async def loadingBar(ctx, message : discord.Message):

    loadingMessage0 = "{0}: \".format(message)
    loadingMessage1 = "{0}: |".format(message)
    loadingMessage2 = "{0}: /".format(message)
    loadingMessage3 = "{0}: -".format(message)

    index = 0

    while True:
        if(index == 0):
            await message.edit(contents = loadingMessage0)
            index = 1
        elif(index == 1):
            await message.edit(contents = loadingMessage1)
            index = 2
        elif(index == 2):
            await message.edit(contents = loadingMessage2)
            index = 3
        elif(index == 1):
            await message.edit(contents = loadingMessage1)
            index = 0

再往下,启动进程的 bot 命令...

@bot.command()
async def downloadSong(ctx, url : str, songname : str):
    
    #Other code that doesn't matter

    message = await ctx.send("Downloading audio")

    _thread = threading.Thread(target=asyncio.run, args=(loadingBar(ctx, message),))
    _thread.start()

    #Function that takes a while

    #Some way to kill thread, never got this far

但是,我收到错误 Task <Task pending coro=<loadingBar() running at bot.py:20> cb=[_run_until_complete_cb() at /Users/user/.pyenv/versions/3.7.3/lib/python3.7/asyncio/base_events.py:158]> got Future <Future pending> attached to a different loop。我是异步编程和不和谐库的新手;有没有更好的方法来做到这一点,如果没有,我做错了什么?

首先,您应该在 while 循环内的迭代之间添加一个延迟,为此使用 asyncio.sleep

其次 - asyncio 和线程并不能真正协同工作,这里使用线程也没有意义,因为它违背了 asyncio 的全部目的,使用 asyncio.create_task 到 运行 协程“在背景”,你可以把它赋值给一个变量,然后调用cancel方法停止任务。

import asyncio


async def loadingBar(ctx, message : discord.Message):
    loadingMessage0 = "{0}: \".format(message)
    loadingMessage1 = "{0}: |".format(message)
    loadingMessage2 = "{0}: /".format(message)
    loadingMessage3 = "{0}: -".format(message)

    index = 0

    while True:
        if(index == 0):
            await message.edit(contents = loadingMessage0)
            index = 1
        elif(index == 1):
            await message.edit(contents = loadingMessage1)
            index = 2
        elif(index == 2):
            await message.edit(contents = loadingMessage2)
            index = 3
        elif(index == 1):
            await message.edit(contents = loadingMessage1)
            index = 0
        await asyncio.sleep(1)  # you can edit the message 5 times per 5 seconds


@bot.command()
async def downloadSong(ctx, url : str, songname : str):
    message = await ctx.send("Downloading audio")
    task = asyncio.create_task(loadingBar(ctx, message))  # starting the coroutine "in the background"

    # Function that takes a while

    task.cancel()  # stopping the background task