如何修复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网关无法及时处理心跳、新消息、交互等核心事件。具体到代码中的问题:
ChatGpt.Ask()如果是同步实现或底层调用阻塞,会直接卡住网关线程- 全局消息队列
_messageHistory无线程安全保护,并发访问会引发异常或阻塞 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
相关产品推荐
相关产品推荐

