如何让Discord机器人与while循环同时运行?
解决Discord机器人与Blender渲染任务并发问题
核心问题分析
bot.run()是阻塞调用,会持续占用事件循环,导致后续渲染代码无法执行- 直接用
asyncio.gather()出错,本质是未正确共享事件循环,或同步渲染任务阻塞了异步循环
修正后的完整代码示例
以下代码将机器人与渲染任务整合到同一异步环境,同时通过线程池处理同步的Blender渲染,避免阻塞事件循环:
import asyncio import discord from discord.ext import commands import subprocess import time from concurrent.futures import ThreadPoolExecutor # 初始化Discord Bot(v2+版本需显式设置Intents) intents = discord.Intents.default() intents.message_content = True # 必须开启消息内容权限才能接收命令 bot = commands.Bot(command_prefix='!', intents=intents) # 共享渲染状态变量(简单场景用全局变量,复杂场景可改用线程安全类) render_start_time = None current_task = None total_tasks = 0 completed_tasks = 0 @bot.event async def on_ready(): print(f'Logged in as {bot.user.name}') @bot.command() async def progress(ctx): global render_start_time, completed_tasks, total_tasks, current_task if not render_start_time: await ctx.send('渲染尚未开始') return elapsed = time.time() - render_start_time avg_time_per_task = elapsed / completed_tasks if completed_tasks > 0 else 0 remaining_tasks = total_tasks - completed_tasks estimated_remaining = avg_time_per_task * remaining_tasks msg = ( f'渲染进度:{completed_tasks}/{total_tasks}\n' f'已用时间:{elapsed:.2f}秒\n' f'当前任务:{current_task}\n' f'预计剩余时间:{estimated_remaining:.2f}秒' ) await ctx.send(msg) async def run_bot(): await bot.start('YOUR_DISCORD_BOT_TOKEN') # 同步Blender渲染函数(放到线程池执行,避免阻塞异步循环) def render_blender_file(file_path): global current_task current_task = file_path # 替换为你的Blender命令行参数 result = subprocess.run( ['blender', '-b', file_path, '-o', 'render_output/', '-F', 'PNG', '-f', '1'], capture_output=True, text=True ) if result.returncode != 0: print(f'渲染失败:{file_path}\n错误信息:{result.stderr}') return result.returncode == 0 async def render_queue(file_list): global render_start_time, total_tasks, completed_tasks render_start_time = time.time() total_tasks = len(file_list) completed_tasks = 0 # 用线程池执行同步渲染任务,max_workers设为1保证按队列顺序执行 with ThreadPoolExecutor(max_workers=1) as executor: for file_path in file_list: success = await asyncio.get_event_loop().run_in_executor(executor, render_blender_file, file_path) if success: completed_tasks += 1 print(f'完成任务 {completed_tasks}/{total_tasks}:{file_path}') print('所有渲染任务完成') async def main(): # 将机器人和渲染任务加入同一事件循环 bot_task = asyncio.create_task(run_bot()) # 替换为你的Blender文件列表 render_task = asyncio.create_task(render_queue(['file1.blend', 'file2.blend', 'file3.blend'])) # 等待两个任务完成(机器人会持续运行,直到出错或被中断) await asyncio.gather(bot_task, render_task) if __name__ == '__main__': try: asyncio.run(main()) except KeyboardInterrupt: print('程序被中断')
关键要点说明
- 避免阻塞事件循环:Blender的
subprocess.run()是同步阻塞调用,必须通过ThreadPoolExecutor和run_in_executor在异步环境中执行,防止卡住机器人的消息处理。 - 状态同步:用全局变量存储渲染进度,确保
!progress命令能获取实时数据;复杂场景可改用线程安全的队列或类来管理状态。 - 正确启动异步任务:用
asyncio.create_task()将机器人和渲染任务加入同一事件循环,再通过asyncio.gather()等待任务完成,替代阻塞的bot.run()。 - Discord权限配置:discord.py v2+需在开发者后台开启
message_content权限,否则机器人无法接收消息命令。
常见问题排查
- "attached to a different loop"错误:确保所有异步任务都由
asyncio.run()统一管理的事件循环执行,不要手动创建多个循环。 - 机器人无响应:检查是否有同步代码阻塞了事件循环,所有耗时操作(渲染、文件IO等)必须放到线程/进程池执行。
内容的提问来源于stack exchange,提问作者EverythingerTruten
相关产品推荐
相关产品推荐

