namespace Dpz.Core.Web.Library;
[Obsolete]
public class ChatCurrentConnections
{
/// <summary>
/// 在线用户
/// </summary>
private static readonly Dictionary<string, VmUserInfo> ConnectionUsers =
new Dictionary<string, VmUserInfo>();
private static readonly object LockObj = new object();
private static ChatCurrentConnections? _instance;
private ChatCurrentConnections() { }
public static ChatCurrentConnections GetInstance()
{
if (_instance != null)
{
return _instance;
}
lock (LockObj)
{
_instance = new ChatCurrentConnections();
return _instance;
}
}
public VmUserInfo this[string index] => ConnectionUsers[index];
public void Add(string connectionId, VmUserInfo user)
{
lock (LockObj)
{
if (ConnectionUsers.ContainsKey(connectionId))
{
ConnectionUsers.Add(connectionId, user);
}
else
{
ConnectionUsers[connectionId] = user;
}
}
}
public VmUserInfo? Get(string connectionId)
{
if (!ConnectionUsers.TryGetValue(connectionId, out var user))
{
return null;
}
return user;
}
public void Remove(string connectionId)
{
if (ConnectionUsers.ContainsKey(connectionId))
{
//ConnectionUsers.Values.Any(x => x.Id == this[connectionId].Id)
lock (LockObj)
{
ConnectionUsers.Remove(connectionId);
}
}
}
public List<VmUserInfo> GetAllOnlineUsers()
{
lock (LockObj)
{
return ConnectionUsers.Select(x => x.Value).ToList();
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
下面按要点说明这段代码的功能、各成员作用、存在的问题(尤其是并发/逻辑错误),以及可行的改进建议。
功能概述
- 这是一个(意图上的)单例类 ChatCurrentConnections,用来维护当前在线用户的映射:以 connectionId(例如 SignalR 的连接 id)为键,VmUserInfo 为值,保存到静态字典 ConnectionUsers 中。
- 提供添加、查询、移除单个连接,以及获取所有在线用户列表的操作。
- 类上标记了 [Obsolete],表示该类已被标记为过时或建议不要再使用(源代码作者可能有替代实现)。
成员和行为
- private static readonly Dictionary<string, VmUserInfo> ConnectionUsers:存储在线用户的字典(静态,共享)。
- private static readonly object LockObj:用于加锁的对象,意图实现线程同步。
- private static ChatCurrentConnections? _instance:单例实例字段。
- private ChatCurrentConnections():私有构造函数,防止外部直接 new。
- public static ChatCurrentConnections GetInstance():返回单例实例(当前实现试图用锁保证线程安全)。
- public VmUserInfo this[string index] => ConnectionUsers[index]:索引器,直接通过 key 取值(无锁)。
- public void Add(string connectionId, VmUserInfo user):将 connectionId 与 user 关联(在方法体中有锁)。
- public VmUserInfo? Get(string connectionId):尝试从字典中取值(无锁)。
- public void Remove(string connectionId):移除指定 connectionId(先检查 ContainsKey,再在锁内 Remove)。
- public List
GetAllOnlineUsers():在锁内把字典的值拷贝成列表返回。
存在的问题和 BUG(重要)
Add 方法逻辑错误(严重)
- 代码: if (ConnectionUsers.ContainsKey(connectionId)) { ConnectionUsers.Add(connectionId, user); } else { ConnectionUsers[connectionId] = user; }
- 意图通常是:如果已存在则覆盖,否则 Add。但当前条件写反了:当字典已有 key 时调用 Add 会抛出 ArgumentException,反而当不存在时使用索引器设置。
- 正确应为: if (!ConnectionUsers.ContainsKey(connectionId)) ConnectionUsers.Add(connectionId, user); else ConnectionUsers[connectionId] = user;
单例实现非严格正确(可能创建多次实例)
- GetInstance 先检查 _instance != null 再 lock,然后在 lock 内直接 new,不再次检查 _instance 是否已经被其他线程创建。这样在两个线程同时通过外部检查后,两个线程都能进入 lock(第二个会等待),第一个创建实例后第二个进入 lock 时又会再创建一个实例并覆盖 _instance。
- 解决办法:在 lock 内再判断一次(双重检查),或使用更简单且安全的模式,例如静态只读实例或 Lazy
。
字典的并发访问不一致
- 虽然很多操作使用 LockObj 加锁,但并非所有读操作都加锁(例如索引器和 Get 方法以及 ContainsKey 前的检查是无锁或分开锁的)。这会导致竞态条件:在无锁读取时字典可能被修改,造成异常(例如在枚举时抛出)、不一致读取或 KeyNotFoundException。
- Remove 方法先做 ContainsKey 再在 lock 内 Remove,期间状态可能被其他线程改变(虽然不会抛,但逻辑不严谨)。
- 返回 List 的做法是把值拷贝了一份(在 lock 内),这是好的——避免外部枚举原始字典。
改进建议(简明)
- 修复 Add 的条件逻辑(见上)。
- 改进单例实现:
- 推荐使用 readonly 静态初始化或 Lazy
: - static readonly ChatCurrentConnections Instance = new ChatCurrentConnections();
- 或 public static ChatCurrentConnections GetInstance() => LazyInstance.Value; private static readonly Lazy
LazyInstance = new(...);
- 或在 lock 中使用双重检查: lock(...) { if (_instance == null) _instance = new ...; }
- 推荐使用 readonly 静态初始化或 Lazy
- 使用线程安全的集合来简化并发处理:
- 将 Dictionary 换成 ConcurrentDictionary<string, VmUserInfo>,这样很多操作可以用 TryAdd/TryUpdate/TryRemove/[] 原子完成,减少手工加锁的错误。
- 保证所有对共享字典的读取/写入要么都在锁内,要么都使用线程安全集合,避免无锁读取和有锁写入混用。
- 索引器应当考虑返回 null 或抛更明确的异常,或者在索引器访问前保证 key 存在(当前直接 ConnectionUsers[index] 会在不存在时抛 KeyNotFoundException)。
- 如果类被标为 Obsolete,最好查找并使用推荐的替代实现(例如应用程序中可能已有更好的在线用户管理方案)。
示例:用 ConcurrentDictionary 的简化版(伪代码)
- private static readonly ConcurrentDictionary<string, VmUserInfo> ConnectionUsers = new();
- Add: ConnectionUsers[connectionId] = user; 或 ConnectionUsers.AddOrUpdate(...)
- Get: ConnectionUsers.TryGetValue(connectionId, out var u) ? u : null
- Remove: ConnectionUsers.TryRemove(connectionId, out _)
- GetAllOnlineUsers: return ConnectionUsers.Values.ToList();
总结
- 这段代码的目的是作为单例管理当前在线用户(connectionId -> VmUserInfo)。
- 实现中存在重要的并发与逻辑问题(Add 的反向判断、单例可能被多次创建、读写锁使用不一致),建议用 ConcurrentDictionary 或正确的单例/锁策略进行修正。若需要,我可以把这份代码改成更安全、简洁的版本(比如用 ConcurrentDictionary + Lazy
),并贴出完整修正后的代码。要不要我把代码修正并给出具体实现?
评论加载中...