You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 14:50:12