Discord.py中VoiceChannel.connect()导致线程挂起问题求助
问题
我正在用Python编写一个简单的Discord机器人,希望机器人能加入语音频道并持续循环播放MP3文件。起初我在on_ready()函数中使用while循环,效果很差。之后尝试用线程将循环与主线程分离,但discord.py采用async/await异步机制,我的实现存在问题。运行线程时,代码执行到await channel.connect()语句就发生挂起,其他命令可正常运行,正常情况下connect()会在数毫秒内返回VoiceClient。请问这是否是跨线程调用函数导致的?有没有更好的解决方案?
相关代码:
import asyncio import threading import discord from discord import app_commands intents = discord.Intents.all() client = discord.Client(intents=intents) discord.Permissions.manage_nicknames = True tree = app_commands.CommandTree(client) def targeter(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(play_sounds()) loop.close() async def play_sounds(): for channel in client.guilds[1].channels: if str(channel.type) == "voice": print("started") player_channel = await channel.connect() print("done") @client.event async def on_ready(): await tree.sync() threading.Thread(target=targeter).start() client.run("token")
运行输出:
>>> started
解决方案
问题根源
确实是跨线程调用导致的问题。discord.py的Client对象绑定在主线程的异步事件循环上,你在新线程中创建了独立的事件循环,跨线程调用channel.connect()时,异步操作无法和主线程的客户端状态正确交互,最终导致挂起。
正确实现方式
不需要额外创建线程,直接利用discord.py的异步机制,用asyncio.create_task在主线程的事件循环中启动播放任务,同时通过VoiceClient的play方法配合after回调实现循环播放。
修正后的代码示例:
import asyncio import discord from discord import app_commands intents = discord.Intents.all() client = discord.Client(intents=intents) tree = app_commands.CommandTree(client) # 替换为你的MP3文件路径 MP3_FILE_PATH = "your_audio_file.mp3" async def play_loop(voice_client): # 定义循环播放的回调 def after_playing(error): if error: print(f"播放出错: {error}") # 重新启动播放,实现循环 asyncio.run_coroutine_threadsafe(play_loop(voice_client), client.loop) if not voice_client.is_playing(): source = discord.FFmpegPCMAudio(MP3_FILE_PATH) voice_client.play(source, after=after_playing) async def join_and_play(): # 找到目标语音频道(这里取第二个服务器的第一个语音频道,可根据需求调整) guild = client.guilds[1] voice_channel = next((ch for ch in guild.channels if ch.type == discord.ChannelType.voice), None) if not voice_channel: print("未找到语音频道") return # 连接到语音频道 voice_client = await voice_channel.connect() print("已连接到语音频道") # 启动循环播放 await play_loop(voice_client) @client.event async def on_ready(): await tree.sync() # 在主线程事件循环中创建播放任务 asyncio.create_task(join_and_play()) client.run("your_bot_token")
关键说明
- 用
asyncio.create_task替代线程,让播放任务运行在discord.py的主线程事件循环中,避免跨线程问题。 - 通过
after回调实现循环播放:每次播放结束后,重新调用播放函数。 - 使用
discord.FFmpegPCMAudio加载MP3文件,确保已安装ffmpeg并配置到环境变量中。
内容的提问来源于stack exchange,提问作者CalciumBromide
相关产品推荐
相关产品推荐

