C#开发Discord Bot遇到线程同步问题求助
解决方案:线程安全字典 + 可靠定时任务
针对多线程操作字典导致数据异常、同时担心lock影响定时更新的问题,给出以下具体解决步骤:
1. 替换为线程安全字典
普通Dictionary<TKey,TValue>不支持多线程并发操作,直接改用ConcurrentDictionary<TKey,TValue>——它内部已实现线程安全的增删改查逻辑,无需手动加锁,能从根源避免数据竞争问题。
首先修改字典定义:
// 替换原来的普通字典 private readonly ConcurrentDictionary<string, string> queue = new ConcurrentDictionary<string, string>(); private readonly ConcurrentDictionary<string, string> blackList = new ConcurrentDictionary<string, string>();
然后修改所有字典操作代码,使用ConcurrentDictionary的安全方法:
// UpdateQueue中的添加操作 queue.TryAdd("test", "test"); blackList.TryAdd("test", "test"); // Discord_ButtonIsPressed中的黑名单添加 blackList.TryAdd("test", args.User.Id.ToString());
读取字典值时推荐用TryGetValue,避免键不存在引发的异常:
if (queue.TryGetValue("test", out var userContent)) { embed.AddField("firstUser", userContent); } else { embed.AddField("firstUser", "队列暂无用户"); }
2. 优化定时任务,保证更新间隔
当前用Thread+while(true)的定时方式不够可靠,改用.NET 6+提供的PeriodicTimer,它会确保每次任务完成后再等待30秒,不会因任务执行耗时导致间隔混乱,同时避免不必要的线程浪费。
修改按钮点击事件中的定时逻辑:
private async void button10_Click(object sender, EventArgs e) { // 初始化30秒间隔的定时器 using var updateTimer = new PeriodicTimer(TimeSpan.FromSeconds(30)); // 启动后台定时任务 _ = Task.Run(async () => { while (await updateTimer.WaitForNextTickAsync()) { await UpdateQueue(); } }); channel = await discord.GetChannelAsync(queueChannelID); discord.ComponentInteractionCreated += Discord_ButtonIsPressed; await discord.ConnectAsync(); await Task.Delay(-1); }
3. 若必须用lock,缩小锁定范围
如果因特殊需求必须使用普通字典+lock,绝对不要把异步操作(比如await message.ModifyAsync)放在lock块内,否则会导致lock持有时间过长,影响定时精度。仅在操作字典的代码段加锁,且每个字典使用单独的锁对象:
// 定义单独的锁对象 private readonly object queueLock = new object(); private readonly object blackListLock = new object(); private async Task UpdateQueue() { // 仅锁定字典操作的代码 lock (queueLock) { queue.Add("test", "test"); } lock (blackListLock) { blackList.Add("test", "test"); } // 异步操作放在lock外,不占用锁资源 DiscordMessage message = await channel.GetMessageAsync(messageID); await message.ModifyAsync(queue["test"]); }
额外提示
- 避免
async void方法中出现未处理的异常,UI事件的async void虽允许,但建议内部添加try-catch块。 - 从数据库读取队列/黑名单数据时,建议一次性读取后批量更新到线程安全字典,减少并发操作次数。
内容的提问来源于stack exchange,提问作者Lok
相关产品推荐
相关产品推荐

