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

如何修复Discord.Net中'A MessageReceived handler阻塞网关任务'错误

解决Discord.Net机器人的"A MessageReceived handler is blocking the gateway task."错误

问题背景

一周前我用C#(Discord.Net)开发Discord机器人,通过/startchat和/endchat两个斜杠命令实现GPT聊天功能,但遇到了"A MessageReceived handler is blocking the gateway task."的报错,机器人代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ChatGPT.Net;
using Discord;
using Discord.Commands;
using Discord.Net;
using Discord.WebSocket;
using Newtonsoft.Json;

public class Program
{
    private DiscordSocketClient _client;
    private CommandService _commands;
    private ChatGpt _chatGpt;
    private bool _isChatting;
    private Queue<string> _messageHistory;
    private List<LogMessage> _logMessages;
    private Dictionary<ulong, List<string>> _userMessageHistory = new Dictionary<ulong, List<string>>();

    public static void Main(string[] args)
        => new Program().RunBotAsync().GetAwaiter().GetResult();

    public async Task RunBotAsync()
    {
        _client = new DiscordSocketClient();
        _commands = new CommandService();
        _chatGpt = new ChatGpt("");
        _isChatting = false;
        _messageHistory = new Queue<string>();
        _logMessages = new List<LogMessage>();
        var config = new DiscordSocketConfig
        {
            GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
        };
        _client.MessageReceived += MessageReceivedAsync;
        _client.Log += Log;

        await RegisterCommandsAsync();

        await _client.LoginAsync(TokenType.Bot, "");
        await _client.StartAsync();

        await Task.Delay(-1);
    }

    private Task Log(LogMessage arg)
    {
        Console.WriteLine(arg);
        _logMessages.Add(arg);
        return Task.CompletedTask;
    }

    private async Task MessageReceivedAsync(SocketMessage arg)
    {
        var message = arg as SocketUserMessage;
        if (message == null || message.Author.IsBot) return;

        var context = new SocketCommandContext(_client, message);

        if (message.Author.IsBot) return;

        if (_isChatting)
        {
            _messageHistory.Enqueue(message.Content);
            string chatInput = string.Join("\n", _messageHistory);

            string chatOutput = await _chatGpt.Ask(chatInput);

            Console.WriteLine($"Пользователь: {message.Content}");
            Console.WriteLine($"Бот: {chatOutput}");

            await context.Channel.SendMessageAsync(Format.Bold("Пользователь:") + $" {message.Content}");
            await context.Channel.SendMessageAsync(Format.Bold("Бот:") + $" {chatOutput}");

            _messageHistory.Enqueue(chatOutput);
            if (_messageHistory.Count > 3)
            {
                _messageHistory.Dequeue();
            }
        }
    }

    private async Task RegisterCommandsAsync()
    {
        _client.Ready += async () =>
        {
            Console.WriteLine("Бот подключен!");

            try
            {
                var startChatCommand = new SlashCommandBuilder()
                    .WithName("startchat")
                    .WithDescription("Начать чат с ChatGPT");
                await _client.CreateGlobalApplicationCommandAsync(startChatCommand.Build());
            }
            catch (ApplicationCommandException exception)
            {
                var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
                Console.WriteLine(json);
            }

            try
            {
                var endChatCommand = new SlashCommandBuilder()
                    .WithName("endchat")
                    .WithDescription("Завершить текущий чат с ChatGPT");
                await _client.CreateGlobalApplicationCommandAsync(endChatCommand.Build());
            }
            catch (ApplicationCommandException exception)
            {
                var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
                Console.WriteLine(json);
            }
        };

        _client.InteractionCreated += async interaction =>
        {
            if (interaction is SocketSlashCommand slashCommand)
            {
                await SlashCommandHandler(slashCommand);
            }
        };
    }

    private async Task SlashCommandHandler(SocketSlashCommand command)
    {
        var channel = command.Channel;
        switch (command.Data.Name)
        {
            case "startchat":
                await StartChatCommand(command.Data, channel);
                break;
            case "endchat":
                await EndChatCommand(command.Data, channel);
                break;
        }
    }

    private async Task StartChatCommand(SocketSlashCommandData commandData, ISocketMessageChannel channel)
    {
        if (!_isChatting)
        {
            _isChatting = true;
            await channel.SendMessageAsync("Чат начат");
        }
        else
        {
            await channel.SendMessageAsync("Чат уже идет");
        }
    }

    private async Task EndChatCommand(SocketSlashCommandData commandData, ISocketMessageChannel channel)
    {
        var targetId = commandData.Id;

        var message = await channel.GetMessageAsync(targetId) as SocketUserMessage;
        var context = new SocketCommandContext(_client, message);

        if (_isChatting)
        {
            _isChatting = false;
            await context.Channel.SendMessageAsync("Чат окончен");
        }
        else
        {
            await context.Channel.SendMessageAsync("Чат окончен. Напишите /startchat для начала");
        }

        var userId = message.Author.Id;
        if (_userMessageHistory.ContainsKey(userId))
        {
            var userMessages = _userMessageHistory[userId];
            Console.WriteLine($"История сообщений пользователя {userId}:");
            foreach (var userMessage in userMessages)
            {
                Console.WriteLine(userMessage);
            }
        }


        Console.WriteLine("Сохраненные логи:");
        foreach (var logMessage in _logMessages)
        {
            Console.WriteLine(logMessage);
        }
    }
}

错误原因

这个报错的核心是**MessageReceived事件处理程序占用网关任务线程时间过长**,导致Discord网关无法及时处理心跳、新消息、交互等核心事件。具体到代码中的问题:

  1. ChatGpt.Ask()如果是同步实现或底层调用阻塞,会直接卡住网关线程
  2. 全局消息队列_messageHistory无线程安全保护,并发访问会引发异常或阻塞
  3. EndChatCommand中用命令ID错误获取消息,会导致空引用异常,进一步拖慢网关

修复步骤

1. 隔离耗时异步操作

把GPT请求放到线程池执行,避免占用网关任务线程:

// 修改MessageReceivedAsync中的GPT调用部分
string chatOutput = await Task.Run(() => _chatGpt.Ask(chatInput));

2. 加锁保证消息历史线程安全

在Program类中添加锁对象,所有操作消息历史的代码都加锁:

// 新增锁对象
private readonly object _historyLock = new object();

// 修改MessageReceivedAsync中的历史操作
lock (_historyLock)
{
    _messageHistory.Enqueue(message.Content);
    string chatInput = string.Join("\n", _messageHistory);
}

// 获取GPT结果后
lock (_historyLock)
{
    _messageHistory.Enqueue(chatOutput);
    if (_messageHistory.Count > 3)
    {
        _messageHistory.Dequeue();
    }
}

3. 修复EndChatCommand的错误逻辑

commandData.Id是斜杠命令的ID,不是消息ID,直接用命令的用户信息即可:

// 修改SlashCommandHandler的调用
private async Task SlashCommandHandler(SocketSlashCommand command)
{
    var channel = command.Channel;
    switch (command.Data.Name)
    {
        case "startchat":
            await StartChatCommand(command, channel);
            break;
        case "endchat":
            await EndChatCommand(command, channel);
            break;
    }
}

// 修改EndChatCommand方法
private async Task EndChatCommand(SocketSlashCommand command, ISocketMessageChannel channel)
{
    if (_isChatting)
    {
        _isChatting = false;
        await channel.SendMessageAsync("Чат окончен");
    }
    else
    {
        await channel.SendMessageAsync("Чат окончен. Напишите /startchat для начала");
    }

    var userId = command.User.Id;
    lock (_historyLock)
    {
        if (_userMessageHistory.ContainsKey(userId))
        {
            var userMessages = _userMessageHistory[userId];
            Console.WriteLine($"История сообщений пользователя {userId}:");
            foreach (var userMessage in userMessages)
            {
                Console.WriteLine(userMessage);
            }
        }
    }

    Console.WriteLine("Сохраненные логи:");
    foreach (var logMessage in _logMessages)
    {
        Console.WriteLine(logMessage);
    }
}

4. 正确初始化DiscordSocketClient

把配置对象传入客户端构造函数:

var config = new DiscordSocketConfig
{
    GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
};
_client = new DiscordSocketClient(config); // 传入config

5. 移除冗余代码

删除MessageReceivedAsync中重复的message.Author.IsBot判断。


内容的提问来源于stack exchange,提问作者Romy Golman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 13:18:09