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

如何在discord.py的on_message事件外运行阻塞式长耗时Python脚本?

解决Discord机器人运行阻塞式长耗时任务的方案

你的核心问题是阻塞式的fetch(wallet)占用了机器人的事件循环,导致心跳中断。下面提供三种可行的方案,都能让任务在后台运行,不影响机器人正常工作,且完成后自动发送结果到Discord频道。

方案一:使用asyncio.to_thread(Python 3.9+ 推荐)

这是最简单的实现方式,Python 3.9+内置的asyncio.to_thread可以直接把阻塞函数放到线程池执行,不阻塞主事件循环。

修改后的机器人代码:

import asyncio
# 其他必要导入...

async def run_fetch_and_report(wallet, channel):
    try:
        # 将阻塞的fetch函数放到线程中执行
        report = await asyncio.to_thread(fetch, wallet)
        # 检测完成后发送结果到频道
        await channel.send(f'钱包 {wallet} 的作弊检测完成:\n{report}')
    except Exception as e:
        await channel.send(f'检测钱包 {wallet} 时出错:{str(e)}')

@bot.event
async def on_message(message):
    if message.author == bot.user:
        return
    msg = message.content.lower()
    channel = message.channel

    if "check wallet" in msg and str(channel) == "gus":
        wallet = msg.split("check wallet")[1].strip()
        # 创建后台任务,无需等待,机器人继续处理其他消息
        bot.loop.create_task(run_fetch_and_report(wallet, channel))
        await message.channel.send(f'已启动对 {wallet} 的作弊检测,完成后会通知你!')
    
    if "hello" in msg:
        await message.channel.send("Hi, where's Shawn?")

方案二:使用线程池(Python 3.8及以下)

如果你的Python版本低于3.9,可以用concurrent.futures.ThreadPoolExecutor手动创建线程池:

修改后的机器人代码:

import asyncio
from concurrent.futures import ThreadPoolExecutor
# 其他必要导入...

# 初始化线程池,根据服务器配置调整最大线程数
executor = ThreadPoolExecutor(max_workers=4)

async def run_fetch_and_report(wallet, channel):
    try:
        loop = asyncio.get_event_loop()
        # 在线程池中执行fetch函数
        report = await loop.run_in_executor(executor, fetch, wallet)
        await channel.send(f'钱包 {wallet} 的作弊检测完成:\n{report}')
    except Exception as e:
        await channel.send(f'检测钱包 {wallet} 时出错:{str(e)}')

@bot.event
async def on_message(message):
    # 其余逻辑同方案一...
    if "check wallet" in msg and str(channel) == "gus":
        wallet = msg.split("check wallet")[1].strip()
        bot.loop.create_task(run_fetch_and_report(wallet, channel))
        await message.channel.send(f'已启动对 {wallet} 的作弊检测,完成后会通知你!')
    # ...

方案三:使用子进程(适合CPU密集型任务)

如果fetch包含大量CPU密集型计算(比如数百万条数据解析),线程会受GIL限制,用子进程可以彻底隔离任务资源:

步骤1:把fetch单独写成脚本(比如cheater_check.py)

import sys
# 导入你的fetch函数所在模块
from your_script import fetch

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("参数错误:请传入钱包地址")
        sys.exit(1)
    wallet = sys.argv[1]
    try:
        report = fetch(wallet)
        print(report)  # 将结果输出到标准输出
    except Exception as e:
        print(f"检测错误:{str(e)}", file=sys.stderr)
        sys.exit(1)

步骤2:修改机器人代码调用子进程

import asyncio
# 其他必要导入...

async def run_fetch_and_report(wallet, channel):
    try:
        # 启动子进程执行检测脚本
        process = await asyncio.create_subprocess_exec(
            "python", "cheater_check.py", wallet,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        # 获取子进程输出
        stdout, stderr = await process.communicate()
        
        if process.returncode == 0:
            report = stdout.decode("utf-8")
            await channel.send(f'钱包 {wallet} 的作弊检测完成:\n{report}')
        else:
            error_msg = stderr.decode("utf-8")
            await channel.send(f'检测钱包 {wallet} 时出错:{error_msg}')
    except Exception as e:
        await channel.send(f'启动检测任务失败:{str(e)}')

@bot.event
async def on_message(message):
    # 其余逻辑同方案一...
    if "check wallet" in msg and str(channel) == "gus":
        wallet = msg.split("check wallet")[1].strip()
        bot.loop.create_task(run_fetch_and_report(wallet, channel))
        await message.channel.send(f'已启动对 {wallet} 的作弊检测,完成后会通知你!')
    # ...

关键注意事项

  • 移除global wallet:全局变量会被并发请求覆盖,直接将钱包地址传递给后台任务即可。
  • 错误处理:必须捕获任务执行中的异常,避免单个任务崩溃影响整个机器人。
  • 资源限制:线程池/子进程的数量不要设置过大,避免服务器资源耗尽。

内容的提问来源于stack exchange,提问作者brian gilbert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 21:34:53