C#运行Discord Bot时如何在不阻塞其他任务的前提下监听控制台用户输入?
适配现有Discord Bot的非阻塞控制台输入实现
你遇到的阻塞问题核心是:同步调用的Console.ReadLine()会独占所在线程,直到用户完成输入才释放资源,直接卡住Bot的Discord消息事件循环。不需要引入复杂第三方组件,用.NET原生的异步任务隔离就能实现双端并行运行。
实现核心逻辑
- 将控制台输入的监听逻辑完全隔离到独立后台任务中,和Discord客户端的主运行流程并行执行,互不抢占线程资源
- 用异步控制台读取方法替代同步阻塞的
Console.ReadLine(),其中使用Console.In.ReadLineAsync()替代传统同步方法是实现非阻塞运行的关键——这个方法在等待用户输入的过程中,会把对应线程释放到线程池处理其他任务,完全不会出现独占线程导致流程卡住的问题 - 完全保留你原有代码里的Discord Bot功能逻辑,只补充输入监听的独立模块,改动量最小,适配C#初学者的理解难度
适配后可直接运行的代码
class Program { public static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); private DiscordSocketClient _client; // 用于实现Bot的优雅关闭 private CancellationTokenSource _cts = new CancellationTokenSource(); public async Task MainAsync() { _client = new DiscordSocketClient(); _client.MessageReceived += CommandHandler; _client.Log += Log; var token = File.ReadAllText("token.txt"); await _client.LoginAsync(TokenType.Bot, token); await _client.StartAsync(); // 并行运行两个流程:后台监听控制台输入、维持Bot运行直到收到退出信号 var consoleListenTask = Task.Run(async () => await ConsoleInputLoopAsync(_cts.Token)); await Task.WhenAny(consoleListenTask, Task.Delay(Timeout.Infinite, _cts.Token)); // 收到退出信号后安全关闭Bot连接 await _client.StopAsync(); } // 跑在独立后台线程的控制台输入循环 private async Task ConsoleInputLoopAsync(CancellationToken ct) { // 循环监听输入,直到收到退出取消信号 while (!ct.IsCancellationRequested) { // 异步读取控制台输入,等待过程不占用线程 var userInput = await Console.In.ReadLineAsync(); if (string.IsNullOrWhiteSpace(userInput)) continue; // 在这里自定义你需要的控制台指令逻辑 userInput = userInput.Trim().ToLower(); switch (userInput) { case "stop": Console.WriteLine("收到停止指令,正在关闭Bot..."); _cts.Cancel(); break; case "status": Console.WriteLine($"当前Bot连接状态:{_client.ConnectionState},已接入服务器数量:{_client.Guilds.Count}"); break; default: Console.WriteLine($"未识别指令:{userInput},可用内置指令:stop、status"); break; } } } private Task Log(LogMessage msg) { Console.WriteLine(msg.ToString()); return Task.CompletedTask; } public Task CommandHandler(SocketMessage message) { // 你原有的Discord消息处理逻辑完全无需修改 string command = message.Content.Substring(0, message.Content.Length).ToLower(); Random rnd = new Random(); string[] greetings = { "hello", "hey", "hi", "sup" }; string[] greetingResponse = { $"Hello {message.Author.Mention}", $"Hey {message.Author.Mention}", $"Hi {message.Author.Mention}" }; int greetingsIndex = rnd.Next(0, greetingResponse.Length); for (int i = 0; i < greetings.Length; i++) { if (command.Equals(greetings[i]) || command.Contains($"{greetings[i]} ")) { _ = message.Channel.SendMessageAsync($@"{greetingResponse[greetingsIndex]}"); } } return Task.CompletedTask; } }
运行效果与注意事项
- 改造后Bot的消息接收、日志输出、自动回复功能完全不受控制台输入等待的影响,两个流程并行运行,不会出现阻塞
- 你可以随时在控制台输入自定义指令,所有操作都不会打断Bot的正常服务
- 注意不要把控制台输入的逻辑直接写在主流程的启动阶段,否则还是会回到同步阻塞的状态,必须用独立后台任务隔离输入监听的循环
- 现有Discord.Net的SocketClient本身是线程安全的,不需要额外写复杂的线程锁逻辑,跨线程调用状态读取、连接关闭的方法不会出现异常
内容的提问来源于stack exchange,提问作者Benn。
相关产品推荐
相关产品推荐

