等待在 Python 中使用 asyncio 下载

Posted

技术标签:

【中文标题】等待在 Python 中使用 asyncio 下载【英文标题】:Awaiting download with asyncio in Python 【发布时间】:2020-10-07 12:50:23 【问题描述】:

我正在开发一个不和谐的机器人,我正在尝试实现一个音乐播放器。我正在使用 discord 和 youtube-dl 包。这是处理播放命令的函数(它仍然是一个原型):

@client.command(brief='Plays the song from the url.')
async def play(ctx, url):
    voice = get(client.voice_clients, guild=ctx.guild)
    if not voice.is_playing():
        try:
            if 'song.mp3' in os.listdir(curr_dir):
                os.remove(os.path.join(curr_dir, 'song.mp3'))
            await download_to_mp3(url)
            voice.play(discord.FFmpegPCMAudio(os.path.join(curr_dir, 'song.mp3')))
            voice.volume = 100
        except youtube_dl.DownloadError:
            await ctx.send('Invalid url.')

还有 download_to_mp3() 函数:

async def download_to_mp3(url):
    opts = 
        'outtmpl': os.path.join(curr_dir, 'song.webm'),
        'format': 'bestaudio/best',
        'postprocessors': [
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '192',
        ],
    
    with youtube_dl.YoutubeDL(opts) as ydl:
        ydl.download([url])

我的意思是它的工作方式是在下载完成时,我仍然可以使用机器人的其他功能。据我了解,等待语句说“暂停 play() 函数的执行,在我等待时做其他事情。当 download_to_mp3 完成时,继续”。但是,它似乎读取下载时发出的命令,但仅在下载完成后才执行它们。如何让它在下载时执行命令?

【问题讨论】:

【参考方案1】:

据我了解,await 语句说“暂停 play() 函数的执行,在我等待时执行其他操作。当 download_to_mp3 完成时,继续”

这正是它的工作原理,前提是您遵循异步规则,基本规则是:在异步期间不要阻塞。由于YoutubeDL.download 显然是一个阻塞函数(你不需要等待它),download_to_mp3 的执行会停止整个事件循环。 download_to_mp3 中没有 await 语句这一事实会提示您该函数只是名称上的异步。

修复它的正确方法是从YoutubeDL 切换到同类型的异步下载器(如果存在)。如果这不是一个选项,或者如果您需要快速修复,您可以使用run_in_executor,它将在不同的线程中执行一个阻塞函数,并返回一个可等待的对象,该对象挂起等待者,直到阻塞函数完成。例如:

@client.command(brief='Plays the song from the url.')
async def play(ctx, url):
    voice = get(client.voice_clients, guild=ctx.guild)
    if not voice.is_playing():
        try:
            if 'song.mp3' in os.listdir(curr_dir):
                os.remove(os.path.join(curr_dir, 'song.mp3'))
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(None, download_to_mp3, url)
            voice.play(discord.FFmpegPCMAudio(
                os.path.join(curr_dir, 'song.mp3')))
            voice.volume = 100
        except youtube_dl.DownloadError:
            await ctx.send('Invalid url.')

# note: download_to_mp3 is now an ordinary function, not an async one
def download_to_mp3(url):
    ... the same definition as before ...

【讨论】:

工作得很好。谢谢!

以上是关于等待在 Python 中使用 asyncio 下载的主要内容,如果未能解决你的问题,请参考以下文章

异步等待函数中的 Python asyncio.semaphore

python协程(4):asyncio

Python asyncio - 使用Task的循环退出已被销毁,但它正在等待处理

等待 asyncio.sleep(1) 在 python 中不起作用

使用 asyncio 等待子进程的结果

asyncio:Python异步编程模块